tag:www.tocode.co.il,2005:/feed2024-03-18T21:00:07+02:00tocode פשוט לתכנת טוב יותרtag:www.tocode.co.il,2005:BlogPost/25872024-03-18T21:00:07+02:002024-03-18T21:00:07+02:00טכניקה וקצבינון פרקTue, 19 Mar 2024 06:00:00 +0200<p>כשהטכניקה טובה אין בעיה לעבוד לאט. הקצב יגיע בקצב שלו. אבל כשהקצב מהיר מאוד קשה לשים לב לפרטים ולשפר את הטכניקה.</p>
<p>בקוד מאוד קל לראות את זה - מתכנתים רצים כדי להספיק את הדדליין ומתרגלים לעבוד בקצב מהיר מדי עבורם, ולא משנה כמה יותר מהר יכתבו את הפיצ'רים לאורך זמן זה תמיד יהיה יותר לאט.</p>
<p>קל ליפול למעגל הזה. פעם אחת דילוורת פיצ'ר יחסית מהר וכולם היו שמחים, ואולי באותו רגע לא שמת לב לבעיות שאותו פיצ'ר יצר. פעם שניה כבר מצפים ממך לאותו קצב, ועם הזמן הציפיה מסביב ומעצמך עובדת לרעתך.</p>
<p>המפתח החוצה זו הטכניקה. ככל שנקפיד לעבוד על טכניקה טובה יותר לפיתוח (בדיקות אוטומטיות, תיעוד, ארגון מחדש של הקוד במקום העתקה, פיתרון בעיות אמיתיות במקום אבסטרקציות מיותרות, ביצועים), כך נגלה שקצב העבודה יורד. וזה מצוין, כי כשהטכניקה טובה הקצב יבוא בקצב שלו.</p>
<p>כשהטכניקה טובה אין בעיה לעבוד לאט. הקצב יגיע בקצב שלו. אבל כשהקצב מהיר מאוד קשה לשים לב לפרטים ולשפר את הטכניקה.</p>
<p>בקוד מאוד קל לראות את זה - מתכנתים רצים כדי להספיק את הדדליין ומתרגלים לעבוד בקצב מהיר מדי עבורם, ולא משנה כמה יותר מהר יכתבו את הפיצ'רים לאורך זמן זה תמיד יהיה יותר לאט.</p>
<p>קל ליפול למעגל הזה. פעם אחת דילוורת פיצ'ר יחסית מהר וכולם היו שמחים, ואולי באותו רגע לא שמת לב לבעיות שאותו פיצ'ר יצר. פעם שניה כבר מצפים ממך לאותו קצב, ועם הזמן הציפיה מסביב ומעצמך עובדת לרעתך.</p>
<p>המפתח החוצה זו הטכניקה. ככל שנקפיד לעבוד על טכניקה טובה יותר לפיתוח (בדיקות אוטומטיות, תיעוד, ארגון מחדש של הקוד במקום העתקה, פיתרון בעיות אמיתיות במקום אבסטרקציות מיותרות, ביצועים), כך נגלה שקצב העבודה יורד. וזה מצוין, כי כשהטכניקה טובה הקצב יבוא בקצב שלו.</p>
tag:www.tocode.co.il,2005:BlogPost/25852024-03-17T17:00:06+02:002024-03-17T17:00:06+02:00היום למדתי לא לסמוך על הקומפיילר של סקאלהינון פרקMon, 18 Mar 2024 06:00:00 +0200<p>אין דבר יותר מתסכל מלבנות על הקומפיילר שיזהה בשבילך שגיאות בטיפוסים ולגלות שגם לו אין מושג. היום למדתי שבסקאלה זה יכול לקרות בקלות באזורים של העבודה המשותפת בין סקאלה ו Java. הנה דוגמה קצרה וטפשית שחבל שאף אחד לא סיפר לי עליה כשהתחלתי ללמוד סקאלה - </p>
<pre><code class="language-scala">import scala.jdk.CollectionConverters._
val m = Map("a" -> 10)
m.asJava.get("a", 2, 3)
</code></pre>
<p>התוצאה היא 0, למרות ש a נמצא במפה. אפשר לטעון ששני הפרמטרים האחרים בלבלו אותו אבל האמת שגם אם נעביר את המחרוזת a לכל הפרמטרים נקבל את אותו 0. הבעיה האמיתית כאן היא ש get של java.util.Map מצפה לקבל פרמטר אחד. העברת יותר פרמטרים לפונקציה היא טעות שהיתה צריכה להתגלות בזמן קומפילציה.</p>
<p>צריך להגיד - מה שמתסכל בסיפור הזה הוא שהקומפיילר כן מזהה טעויות דומות אחרות, למשל הקוד הזה לא מתקמפל כי מנסים להעביר מספר פרמטרים לא נכון לפונקציה של Java:</p>
<pre><code class="language-scala">val m = Map("a" -> 10)
m.asJava.getOrDefault("a", 1, 2)
-- Error: ----------------------------------------------------------------------
2 |m.asJava.getOrDefault("a", 1, 2)
| ^
|too many arguments for method getOrDefault in trait Map: (x$0: Object, x$1: V): V
1 error found
</code></pre>
<p>אבל כשקומפיילר תופס בעיות רק ב 99% מהמקרים זה לפעמים יותר מתסכל מאשר שלא יתפוס בעיות בכלל.</p>
<p>אין דבר יותר מתסכל מלבנות על הקומפיילר שיזהה בשבילך שגיאות בטיפוסים ולגלות שגם לו אין מושג. היום למדתי שבסקאלה זה יכול לקרות בקלות באזורים של העבודה המשותפת בין סקאלה ו Java. הנה דוגמה קצרה וטפשית שחבל שאף אחד לא סיפר לי עליה כשהתחלתי ללמוד סקאלה - </p>
<pre><code class="language-scala">import scala.jdk.CollectionConverters._
val m = Map("a" -> 10)
m.asJava.get("a", 2, 3)
</code></pre>
<p>התוצאה היא 0, למרות ש a נמצא במפה. אפשר לטעון ששני הפרמטרים האחרים בלבלו אותו אבל האמת שגם אם נעביר את המחרוזת a לכל הפרמטרים נקבל את אותו 0. הבעיה האמיתית כאן היא ש get של java.util.Map מצפה לקבל פרמטר אחד. העברת יותר פרמטרים לפונקציה היא טעות שהיתה צריכה להתגלות בזמן קומפילציה.</p>
<p>צריך להגיד - מה שמתסכל בסיפור הזה הוא שהקומפיילר כן מזהה טעויות דומות אחרות, למשל הקוד הזה לא מתקמפל כי מנסים להעביר מספר פרמטרים לא נכון לפונקציה של Java:</p>
<pre><code class="language-scala">val m = Map("a" -> 10)
m.asJava.getOrDefault("a", 1, 2)
-- Error: ----------------------------------------------------------------------
2 |m.asJava.getOrDefault("a", 1, 2)
| ^
|too many arguments for method getOrDefault in trait Map: (x$0: Object, x$1: V): V
1 error found
</code></pre>
<p>אבל כשקומפיילר תופס בעיות רק ב 99% מהמקרים זה לפעמים יותר מתסכל מאשר שלא יתפוס בעיות בכלל.</p>
tag:www.tocode.co.il,2005:BlogPost/25842024-03-16T23:19:14+02:002024-03-16T23:19:14+02:00פיתרון Advent Of Code 2023 יום 14 בסקאלה (חלק ראשון)ינון פרקSun, 17 Mar 2024 06:00:00 +0200<p>עוד שבוע מתחיל וסופי שבוע הם זמן מצוין לפתור עוד תרגיל של Advent Of Code. הפעם אנחנו ביום 14 ועדיין עם מטריצות.</p>
<p>עוד שבוע מתחיל וסופי שבוע הם זמן מצוין לפתור עוד תרגיל של Advent Of Code. הפעם אנחנו ביום 14 ועדיין עם מטריצות.</p>
tag:www.tocode.co.il,2005:BlogPost/25822024-03-15T22:00:06+02:002024-03-15T22:00:06+02:00סטייט ובחירת קומפוננטות ריאקטינון פרקSat, 16 Mar 2024 06:00:00 +0200<p>ריאקט התחילה בתור ספריית UI ולכן זה לא היה מוזר לחשוב על קומפוננטה בתור "החלק בעמוד שמשתנה יחד". הסטייט של הקומפוננטה הוא המידע ששינוי שלו מוביל לשינוי ב UI ואם יש מידע שצריך להשפיע על כמה קומפוננטות נשמור אותו באיזשהו מקום חיצוני (פלאקס, זוכרים?) ונחבר את הקומפוננטות לשם. הדבר החשוב בחלוקה לקומפוננטות היה להיות מסוגלים לעשות Reasoning על החלקים בעמוד. להיות מסוגלים להבין למה הופיע עכשיו הסימן שיש הודעה חדשה.</p>
<p>אבל כל זה היה הגיוני ב 2015. אולי ב 2018.</p>
<p>היום ובמיוחד מאז Hooks אנחנו חושבים על קומפוננטה בתור הדבר בעמוד שחוזר על עצמו בכל מיני מקומות והקשרים. בתור ה Building Block של ארכיטקטורה מבוססת קומפוננטות. משהו שיש לו לוגיקה, משהו שמזיז דברים ביישום. ריאקט, למרות מה שהם כותבים בכותרת, היא כבר לא ספריה רק ל UI או לפחות אנשים לא משתמשים בה רק באופן הזה.</p>
<p>וכך נולד הקונפליקט- האם ליצור קומפוננטות לפי סמיכות מבחינת סטייט או לפי ממשק חיצוני ואפשרות לשימוש חוזר? לפעמים שני השיקולים האלה הולכים יחד, לפעמים הם מתנגשים, ובכל מקרה צורת החשיבה שמובילה לכל אחד שונה. </p>
<p>להשתמש בסטייט בתור קו מנחה לבחירת קומפוננטות זה קשה כי האינטואיציה שלנו כמתכנתים מחפשת "לסדר" את הקוד לאלמנטים לוגיים. במקרה של ריאקט זה שווה את המאמץ לחשוב כמו מפתחי UI.</p>
<p>ריאקט התחילה בתור ספריית UI ולכן זה לא היה מוזר לחשוב על קומפוננטה בתור "החלק בעמוד שמשתנה יחד". הסטייט של הקומפוננטה הוא המידע ששינוי שלו מוביל לשינוי ב UI ואם יש מידע שצריך להשפיע על כמה קומפוננטות נשמור אותו באיזשהו מקום חיצוני (פלאקס, זוכרים?) ונחבר את הקומפוננטות לשם. הדבר החשוב בחלוקה לקומפוננטות היה להיות מסוגלים לעשות Reasoning על החלקים בעמוד. להיות מסוגלים להבין למה הופיע עכשיו הסימן שיש הודעה חדשה.</p>
<p>אבל כל זה היה הגיוני ב 2015. אולי ב 2018.</p>
<p>היום ובמיוחד מאז Hooks אנחנו חושבים על קומפוננטה בתור הדבר בעמוד שחוזר על עצמו בכל מיני מקומות והקשרים. בתור ה Building Block של ארכיטקטורה מבוססת קומפוננטות. משהו שיש לו לוגיקה, משהו שמזיז דברים ביישום. ריאקט, למרות מה שהם כותבים בכותרת, היא כבר לא ספריה רק ל UI או לפחות אנשים לא משתמשים בה רק באופן הזה.</p>
<p>וכך נולד הקונפליקט- האם ליצור קומפוננטות לפי סמיכות מבחינת סטייט או לפי ממשק חיצוני ואפשרות לשימוש חוזר? לפעמים שני השיקולים האלה הולכים יחד, לפעמים הם מתנגשים, ובכל מקרה צורת החשיבה שמובילה לכל אחד שונה. </p>
<p>להשתמש בסטייט בתור קו מנחה לבחירת קומפוננטות זה קשה כי האינטואיציה שלנו כמתכנתים מחפשת "לסדר" את הקוד לאלמנטים לוגיים. במקרה של ריאקט זה שווה את המאמץ לחשוב כמו מפתחי UI.</p>
tag:www.tocode.co.il,2005:BlogPost/25812024-03-14T19:00:06+02:002024-03-14T19:00:06+02:00כמה מתכנתים צריך בשביל להתחבר ל S3?ינון פרקFri, 15 Mar 2024 06:00:00 +0200<p>מתכנת חכם אחד בנה תשתית משוגעת לחיבור ל S3 בתור POC. הוא רצה להראות ששמירת הקבצים על S3 תוכל לשפר את הביצועים במערכת ולאפשר גדילה אופקית. הפרויקט אומנם נקבר בגלל חילופי הנהלה אבל הבסיס של הקוד כבר הוכנס למערכת.</p>
<p>מתכנת חכם שני קיבל בקשה מלקוח לחבר לחשבון שלו את הקבצים שכבר שמורים ללקוח על S3. אצלו בצוות מתכנתים קיבלו קרדיט לפי כמה באגים הם פותרים ולכן היה חשוב לגרום לדברים לעבוד מהר ולהמשיך לסיפור הבא. הוא ידע שיש איזה תשתית בסיסית איפשהו אבל למי היה זמן להבין איך זה עובד אז הוא בנה מנגנון חדש וממוקד שעבד ממש בסדר.</p>
<p>מתכנת חכם שלישי גילה שמישהו כבר בנה ספרייה שמתחברת עם S3 וגם בונה אבסטרקציה כללית מעל איחסון קבצים בענן (כך שאפשר יהיה לעבור מהר לעננים אחרים). הוא החליט לממש באמצעותה חיבור ל S3 עבור פיצ'ר חדש אבל לא רצה להסתכן בלשבור קוד ישן ולא בדוק, ולכן לא ביצע מיגרציה מלאה.</p>
<p>מנהל חכם אחד שמע שהרבה לקוחות רוצים לאחסן קבצים על S3 וידע שעכשיו Micro Services הם ה-דבר, אז הוא מצא צוות באוקראינה שיבנו מיקרו סרביס עם חיבור ל S3. נכון שהסרביס כתוב ב Erlang אבל הבטיחו לו שהביצועים יהיו מצוינים ושהאוקראינים יוכלו לפתור כל בעיה בלי לגעת בשאר המערכת. </p>
<p>מתכנת חכם רביעי היה צריך לעדכן את מנגנון אחסון הקבצים של הלקוחות. האוקראינים לא זמינים כבר שנתיים וכאן אף אחד לא יודע Erlang. את הספריה שהשלישי הכניס הוא לא הכיר וממילא היא כבר לא נתמכת ושני המנגנונים האחרים לא היו מספיק גנריים או קלים לשימוש. מזל שבדיוק יצאה ספריית חיבור חדשה ל S3 עם ממשק חדשני ואבטחת מידע משופרת.</p>
<p>כך נראה חוב טכני. כך נראית מערכת שכבר קשה להתקדם עם פיצ'רים כי כל שינוי שובר משהו אחר. זה הפיך אבל דורש עבודה והיום הוא בדיוק יום מצוין להתחיל <a target="_blank" href="https://xkcd.com/927/">לאחד מנגנונים</a>.</p>
<p>מתכנת חכם אחד בנה תשתית משוגעת לחיבור ל S3 בתור POC. הוא רצה להראות ששמירת הקבצים על S3 תוכל לשפר את הביצועים במערכת ולאפשר גדילה אופקית. הפרויקט אומנם נקבר בגלל חילופי הנהלה אבל הבסיס של הקוד כבר הוכנס למערכת.</p>
<p>מתכנת חכם שני קיבל בקשה מלקוח לחבר לחשבון שלו את הקבצים שכבר שמורים ללקוח על S3. אצלו בצוות מתכנתים קיבלו קרדיט לפי כמה באגים הם פותרים ולכן היה חשוב לגרום לדברים לעבוד מהר ולהמשיך לסיפור הבא. הוא ידע שיש איזה תשתית בסיסית איפשהו אבל למי היה זמן להבין איך זה עובד אז הוא בנה מנגנון חדש וממוקד שעבד ממש בסדר.</p>
<p>מתכנת חכם שלישי גילה שמישהו כבר בנה ספרייה שמתחברת עם S3 וגם בונה אבסטרקציה כללית מעל איחסון קבצים בענן (כך שאפשר יהיה לעבור מהר לעננים אחרים). הוא החליט לממש באמצעותה חיבור ל S3 עבור פיצ'ר חדש אבל לא רצה להסתכן בלשבור קוד ישן ולא בדוק, ולכן לא ביצע מיגרציה מלאה.</p>
<p>מנהל חכם אחד שמע שהרבה לקוחות רוצים לאחסן קבצים על S3 וידע שעכשיו Micro Services הם ה-דבר, אז הוא מצא צוות באוקראינה שיבנו מיקרו סרביס עם חיבור ל S3. נכון שהסרביס כתוב ב Erlang אבל הבטיחו לו שהביצועים יהיו מצוינים ושהאוקראינים יוכלו לפתור כל בעיה בלי לגעת בשאר המערכת. </p>
<p>מתכנת חכם רביעי היה צריך לעדכן את מנגנון אחסון הקבצים של הלקוחות. האוקראינים לא זמינים כבר שנתיים וכאן אף אחד לא יודע Erlang. את הספריה שהשלישי הכניס הוא לא הכיר וממילא היא כבר לא נתמכת ושני המנגנונים האחרים לא היו מספיק גנריים או קלים לשימוש. מזל שבדיוק יצאה ספריית חיבור חדשה ל S3 עם ממשק חדשני ואבטחת מידע משופרת.</p>
<p>כך נראה חוב טכני. כך נראית מערכת שכבר קשה להתקדם עם פיצ'רים כי כל שינוי שובר משהו אחר. זה הפיך אבל דורש עבודה והיום הוא בדיוק יום מצוין להתחיל <a target="_blank" href="https://xkcd.com/927/">לאחד מנגנונים</a>.</p>
tag:www.tocode.co.il,2005:BlogPost/25802024-03-13T16:00:07+02:002024-03-13T16:00:07+02:00לא ככה משתמשים בזהינון פרקThu, 14 Mar 2024 06:00:00 +0200<p>אין לי ספק שלסטיב ג'ובס היו כוונות טובות כשהוא האשים את המשתמשים שמחזיקים לא נכון את הטלפון שלו. הוא ראה את כל השעות שהם בילו במעבדות בבדיקות, את כל הנסיינים שניסו כל פונקציה בטלפון, את הבדיקות האוטומטיות, את הלוגים, את מנגנוני ההגנה והרגולציות. </p>
<p>והנה המוצר יוצא לשוק והמשתמשים עושים איתו דברים שאף אחד לא חשב עליהם בזמן הפיתוח.</p>
<p>מאיפה יש להם את החוצפה?</p>
<p>וזה לא רק האייפון...</p>
<p>איך הם לוחצים על כפתור פעמיים לפני שסיימתי לטפל בלחיצה הראשונה?</p>
<p>למה הם שומרים את הסטייט כל כך גבוה בעץ הקומפוננטות? הרי אמרתי לכם שזה יביא ליותר מדי רנדרים.</p>
<p>למה הם מנסים לתשאל את בסיס הנתונים בלי אינדקסים? ברור שזה יעבוד לאט.</p>
<p>מה פתאום הם בונים שאילתת SQL מקלט שמגיע בטופס? הרי אמרתי להם שצריך לנקות את הקלט לפני שמשתמשים בו.</p>
<p>"לא ככה משתמשים בזה" לא עובד. אנשים ימשיכו להשתמש לא נכון במוצרים שלנו, וימשיכו לבוא בטענות. כדאי לזכור:</p>
<ol>
<li><p>בתור משתמשים חשוב לדעת איך היוצר המקורי תכנן שנשתמש במוצר. לא חייבים להשתמש בפריימוורק לפי החוקים אבל חשוב להכיר ולהבין אותם כדי להבין איך בדיוק זה יישבר כשנחזיק את זה לא נכון.</p></li>
<li><p>בתור יוצרים עלינו להיות מוכנים לביקורת על המוצרים שלנו ויש לנו את הבחירה - לתקן או להתעקש. ריאקט אומנם מתעקשים על מנגנון ניהול הסטייט שלהם אבל מנגנוני ORM פתרו כמעט לגמרי את הבעיה שאנשים בונים שאילתות SQL בצורה ידנית ולא מאובטחת. גם כשמחליטים "להתעקש" אפשר לוודא שהמשתמשים שלנו מבינים מה הם עושים לא בסדר ואיך מתקנים. בריאקט בגלל זה דואגים להדפיס אזהרה כל פעם שאנחנו מרנדרים קומפוננטות בלולאה בלי key. בסיס הנתונים JanusGraph מדפיס לי אזהרה ללוג כל פעם שאני שולח שאילתה ואין לו אינדקס מתאים בשבילה. </p></li>
</ol>
<p>בסוף ברור שזה סיפור של תיאום ציפיות - אף אחד לא מצפה לזרוק את האייפון מקומה רביעית ושימשיך לעבוד, אבל אנחנו כן מצפים שנוכל להחזיק אותו איך שנרצה ועדיין להוציא שיחות. ככל שנתקשר טוב יותר עם המשתמשים שלנו ונבין את הציפיות שלהם נוכל לבנות מוצרים שיתאמו ואף יתעלו מעל אותן ציפיות.</p>
<p>אין לי ספק שלסטיב ג'ובס היו כוונות טובות כשהוא האשים את המשתמשים שמחזיקים לא נכון את הטלפון שלו. הוא ראה את כל השעות שהם בילו במעבדות בבדיקות, את כל הנסיינים שניסו כל פונקציה בטלפון, את הבדיקות האוטומטיות, את הלוגים, את מנגנוני ההגנה והרגולציות. </p>
<p>והנה המוצר יוצא לשוק והמשתמשים עושים איתו דברים שאף אחד לא חשב עליהם בזמן הפיתוח.</p>
<p>מאיפה יש להם את החוצפה?</p>
<p>וזה לא רק האייפון...</p>
<p>איך הם לוחצים על כפתור פעמיים לפני שסיימתי לטפל בלחיצה הראשונה?</p>
<p>למה הם שומרים את הסטייט כל כך גבוה בעץ הקומפוננטות? הרי אמרתי לכם שזה יביא ליותר מדי רנדרים.</p>
<p>למה הם מנסים לתשאל את בסיס הנתונים בלי אינדקסים? ברור שזה יעבוד לאט.</p>
<p>מה פתאום הם בונים שאילתת SQL מקלט שמגיע בטופס? הרי אמרתי להם שצריך לנקות את הקלט לפני שמשתמשים בו.</p>
<p>"לא ככה משתמשים בזה" לא עובד. אנשים ימשיכו להשתמש לא נכון במוצרים שלנו, וימשיכו לבוא בטענות. כדאי לזכור:</p>
<ol>
<li><p>בתור משתמשים חשוב לדעת איך היוצר המקורי תכנן שנשתמש במוצר. לא חייבים להשתמש בפריימוורק לפי החוקים אבל חשוב להכיר ולהבין אותם כדי להבין איך בדיוק זה יישבר כשנחזיק את זה לא נכון.</p></li>
<li><p>בתור יוצרים עלינו להיות מוכנים לביקורת על המוצרים שלנו ויש לנו את הבחירה - לתקן או להתעקש. ריאקט אומנם מתעקשים על מנגנון ניהול הסטייט שלהם אבל מנגנוני ORM פתרו כמעט לגמרי את הבעיה שאנשים בונים שאילתות SQL בצורה ידנית ולא מאובטחת. גם כשמחליטים "להתעקש" אפשר לוודא שהמשתמשים שלנו מבינים מה הם עושים לא בסדר ואיך מתקנים. בריאקט בגלל זה דואגים להדפיס אזהרה כל פעם שאנחנו מרנדרים קומפוננטות בלולאה בלי key. בסיס הנתונים JanusGraph מדפיס לי אזהרה ללוג כל פעם שאני שולח שאילתה ואין לו אינדקס מתאים בשבילה. </p></li>
</ol>
<p>בסוף ברור שזה סיפור של תיאום ציפיות - אף אחד לא מצפה לזרוק את האייפון מקומה רביעית ושימשיך לעבוד, אבל אנחנו כן מצפים שנוכל להחזיק אותו איך שנרצה ועדיין להוציא שיחות. ככל שנתקשר טוב יותר עם המשתמשים שלנו ונבין את הציפיות שלהם נוכל לבנות מוצרים שיתאמו ואף יתעלו מעל אותן ציפיות.</p>
tag:www.tocode.co.il,2005:BlogPost/25792024-03-12T20:00:07+02:002024-03-12T20:00:07+02:00למה אני לא מתלהב מ React Forgetינון פרקWed, 13 Mar 2024 06:00:00 +0200<p>אם עדיין לא שמעתם ככל הנראה ריאקט 19 תשוחרר עם תמיכה ב React Forget <a target="_blank" href="https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024">הקומפיילר החדש</a> מבית ריאקט שאמור להוסיף useMemo אוטומטית לתוכניות שלנו. הנימוקים שלהם לפיתוח הפיצ׳ר (מתוך הפוסט) היו-</p>
<ol>
<li><p>כתיבת useMemo ידנית מלכלכת את הקוד.</p></li>
<li><p>קל לטעות כשצריך להחליט ידנית למה לעשות Memoization.</p></li>
<li><p>תוספת הקוד מעמיסה על התחזוקה.</p></li>
</ol>
<p>הנה משפט המפתח מתוך הפוסט-</p>
<blockquote>
<p>Our vision is for React to automatically re-render just the right parts of the UI when state changes, without compromising on React’s core mental model.</p>
</blockquote>
<p>ומהו אותו מודל מנטלי בליבה של ריאקט? גם פה הם עונים שהממשק הוא בסך הכל פונקציה על מצב האפליקציה, או במילים אחרות שמספיק להסתכל על פונקציית הקומפוננטה ועל הסטייט כדי לדעת מה יופיע על המסך.</p>
<p>ועכשיו אפשר לחזור לכותרת. עם כל ההסברים של החברים בריאקט, איך קרה שהבלוגר הזה לא הכי מתלהב מריאקט-שוכח. הנה הסיבות שלי-</p>
<ol>
<li><p>אין בזה צורך, או יותר נכון הצורך היחיד הוא יחסי ציבור. בכל פרויקט ריאקט שעבדתי עליו בעיות הביצועים נוצרו בגלל חוסר הבנה של איך ריאקט עובד ועוד יותר גרוע חוסר הבנה של המרחק בין קוד צד שרת לקוד צד לקוח. ברוב מוחלט של הסיטואציות אפשר היה להחליף את useMemo בארגון אחר של הקוד הקומפוננטות ולפתור את בעיות הביצועים (וגם לקבל קוד טוב יותר).</p></li>
<li><p>הוספת "קוד אוטומטי" רק מסבכת את ההבנה והתחזוקה. קוד שאני לא רואה עדיין נמצא שם, ואני צריך לזכור אותו ולחשוב עליו כל הזמן.</p></li>
<li><p>הוספת מנגנונים כמו React Forget רק מרחיקה אותנו מפיתרון הבעיות האמיתיות של ריאקט. במובן הזה סיגנלים היו יכולים להיות תוספת הרבה יותר מועילה ומעניינת.</p></li>
</ol>
<p>האם React Forget יגרום לאפליקציות שלנו לעבוד מהר יותר? כנראה שכן. האם הוא יביא איתו בעיות חדשות שאף אחד לא היה צריך בשביל שיפור מינורי בביצועים? כנראה שגם כן.</p>
<p>אם עדיין לא שמעתם ככל הנראה ריאקט 19 תשוחרר עם תמיכה ב React Forget <a target="_blank" href="https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024">הקומפיילר החדש</a> מבית ריאקט שאמור להוסיף useMemo אוטומטית לתוכניות שלנו. הנימוקים שלהם לפיתוח הפיצ׳ר (מתוך הפוסט) היו-</p>
<ol>
<li><p>כתיבת useMemo ידנית מלכלכת את הקוד.</p></li>
<li><p>קל לטעות כשצריך להחליט ידנית למה לעשות Memoization.</p></li>
<li><p>תוספת הקוד מעמיסה על התחזוקה.</p></li>
</ol>
<p>הנה משפט המפתח מתוך הפוסט-</p>
<blockquote>
<p>Our vision is for React to automatically re-render just the right parts of the UI when state changes, without compromising on React’s core mental model.</p>
</blockquote>
<p>ומהו אותו מודל מנטלי בליבה של ריאקט? גם פה הם עונים שהממשק הוא בסך הכל פונקציה על מצב האפליקציה, או במילים אחרות שמספיק להסתכל על פונקציית הקומפוננטה ועל הסטייט כדי לדעת מה יופיע על המסך.</p>
<p>ועכשיו אפשר לחזור לכותרת. עם כל ההסברים של החברים בריאקט, איך קרה שהבלוגר הזה לא הכי מתלהב מריאקט-שוכח. הנה הסיבות שלי-</p>
<ol>
<li><p>אין בזה צורך, או יותר נכון הצורך היחיד הוא יחסי ציבור. בכל פרויקט ריאקט שעבדתי עליו בעיות הביצועים נוצרו בגלל חוסר הבנה של איך ריאקט עובד ועוד יותר גרוע חוסר הבנה של המרחק בין קוד צד שרת לקוד צד לקוח. ברוב מוחלט של הסיטואציות אפשר היה להחליף את useMemo בארגון אחר של הקוד הקומפוננטות ולפתור את בעיות הביצועים (וגם לקבל קוד טוב יותר).</p></li>
<li><p>הוספת "קוד אוטומטי" רק מסבכת את ההבנה והתחזוקה. קוד שאני לא רואה עדיין נמצא שם, ואני צריך לזכור אותו ולחשוב עליו כל הזמן.</p></li>
<li><p>הוספת מנגנונים כמו React Forget רק מרחיקה אותנו מפיתרון הבעיות האמיתיות של ריאקט. במובן הזה סיגנלים היו יכולים להיות תוספת הרבה יותר מועילה ומעניינת.</p></li>
</ol>
<p>האם React Forget יגרום לאפליקציות שלנו לעבוד מהר יותר? כנראה שכן. האם הוא יביא איתו בעיות חדשות שאף אחד לא היה צריך בשביל שיפור מינורי בביצועים? כנראה שגם כן.</p>
tag:www.tocode.co.il,2005:BlogPost/25782024-03-11T18:00:08+02:002024-03-11T18:00:08+02:00מאיפה הגיעה ה Z בסוף הזמן?ינון פרקTue, 12 Mar 2024 06:00:00 +0200<p>בשביל לקחת בסקאלה (או Java) את השעה הנוכחית ולשמור אותה כמחרוזת אוכל להשתמש בקוד הבא:</p>
<pre><code class="language-scala">import java.time.*
import java.time.format.DateTimeFormatter
LocalTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME)
</code></pre>
<p>והמחרוזת שתתקבל עשויה להיראות כך:</p>
<pre><code class="language-sh">"16:51:44.748375"
</code></pre>
<p>אבל כשמסתכלים בבסיסי נתונים וברשת אנחנו נתקלים במחרוזות דומות שמסתיימות באות Z לדוגמה:</p>
<pre><code class="language-sh">14:54:10.140198Z
</code></pre>
<p>למה הם התכוונו? מאיפה ה Z הגיע? ואיך אני גם מקבל אחד?</p>
<p>אז בואו נתחיל בקוד בשביל ליצור מחרוזת זמן עם ה Z בסוף אני לא צריך לשרשר אותו ידנית אלא להשתמש בקוד מקביל שכולל התיחסות לאזור זמן:</p>
<pre><code class="language-scala">OffsetTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_OFFSET_TIME)
</code></pre>
<p>ה Z מסמן שזוהי תווית זמן באזור זמן UTC. אזורי זמן אחרים כבר מקבלים סימון יותר ברור לדוגמה עבור ישראל נקבל:</p>
<pre><code class="language-scala">scala> OffsetTime.now(ZoneId.of("Israel")).format(DateTimeFormatter.ISO_OFFSET_TIME);
val res5: String = 16:57:24.769026+02:00
</code></pre>
<p>וכן היה אפשר במקום ה Z לכתוב משהו כמו פלוס אפס, אבל כנראה שזה לא מספיק מבלבל בשביל מי שכותב תקנים לדברים כאלה.</p>
<p>כשאנחנו בונים מערכת שצריכה לתמוך בכמה אזורי זמן יש לנו שתי אפשרויות מרכזיות - האחת (פשוטה יותר ולדעתי מומלצת), להחליט על אזור זמן בו נשמור את כל השעות במערכת ולשמור בבסיס הנתונים שעה ללא אזור זמן. במצב כזה כל פעם לפני שמירה בבסיס הנתונים נרצה להמיר את השעה שיש לנו לשעה באזור הזמן של בסיס הנתונים, ובכל קריאה נרצה לבצע המרה הפוכה. </p>
<p>אופציה שניה יותר מורכבת היא לשמור את המידע כולל אזור הזמן בבסיס הנתונים. בשביל זה אנחנו יכולים להפוך את השעה למחרוזת הכוללת אזור זמן (עם ה Z בסוף ל UTC, או עם Offset לאזורי זמן אחרים). במצב כזה לא נדרשת המרה אבל כל קוד המערכת יהיה מורכב יותר כי תמיד צריך לחשוב באיזה אזור זמן אנחנו ולעבוד עם סט המחלקות המתאים.</p>
<p>בשביל לקחת בסקאלה (או Java) את השעה הנוכחית ולשמור אותה כמחרוזת אוכל להשתמש בקוד הבא:</p>
<pre><code class="language-scala">import java.time.*
import java.time.format.DateTimeFormatter
LocalTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME)
</code></pre>
<p>והמחרוזת שתתקבל עשויה להיראות כך:</p>
<pre><code class="language-sh">"16:51:44.748375"
</code></pre>
<p>אבל כשמסתכלים בבסיסי נתונים וברשת אנחנו נתקלים במחרוזות דומות שמסתיימות באות Z לדוגמה:</p>
<pre><code class="language-sh">14:54:10.140198Z
</code></pre>
<p>למה הם התכוונו? מאיפה ה Z הגיע? ואיך אני גם מקבל אחד?</p>
<p>אז בואו נתחיל בקוד בשביל ליצור מחרוזת זמן עם ה Z בסוף אני לא צריך לשרשר אותו ידנית אלא להשתמש בקוד מקביל שכולל התיחסות לאזור זמן:</p>
<pre><code class="language-scala">OffsetTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_OFFSET_TIME)
</code></pre>
<p>ה Z מסמן שזוהי תווית זמן באזור זמן UTC. אזורי זמן אחרים כבר מקבלים סימון יותר ברור לדוגמה עבור ישראל נקבל:</p>
<pre><code class="language-scala">scala> OffsetTime.now(ZoneId.of("Israel")).format(DateTimeFormatter.ISO_OFFSET_TIME);
val res5: String = 16:57:24.769026+02:00
</code></pre>
<p>וכן היה אפשר במקום ה Z לכתוב משהו כמו פלוס אפס, אבל כנראה שזה לא מספיק מבלבל בשביל מי שכותב תקנים לדברים כאלה.</p>
<p>כשאנחנו בונים מערכת שצריכה לתמוך בכמה אזורי זמן יש לנו שתי אפשרויות מרכזיות - האחת (פשוטה יותר ולדעתי מומלצת), להחליט על אזור זמן בו נשמור את כל השעות במערכת ולשמור בבסיס הנתונים שעה ללא אזור זמן. במצב כזה כל פעם לפני שמירה בבסיס הנתונים נרצה להמיר את השעה שיש לנו לשעה באזור הזמן של בסיס הנתונים, ובכל קריאה נרצה לבצע המרה הפוכה. </p>
<p>אופציה שניה יותר מורכבת היא לשמור את המידע כולל אזור הזמן בבסיס הנתונים. בשביל זה אנחנו יכולים להפוך את השעה למחרוזת הכוללת אזור זמן (עם ה Z בסוף ל UTC, או עם Offset לאזורי זמן אחרים). במצב כזה לא נדרשת המרה אבל כל קוד המערכת יהיה מורכב יותר כי תמיד צריך לחשוב באיזה אזור זמן אנחנו ולעבוד עם סט המחלקות המתאים.</p>
tag:www.tocode.co.il,2005:BlogPost/25762024-03-10T17:00:07+02:002024-03-10T17:00:07+02:00על גיטהאב, חתימה על קומיטים ולמי אכפתינון פרקMon, 11 Mar 2024 06:00:00 +0200<p>בואו נתחיל בניסוי (רק לאמיצים שבחבורה) - משורת הפקודה שנו את שמכם ואת כתובת הדואר האלקטרוני שגיט חושב שיש לכם עם הפקודות:</p>
<pre><code class="language-sh">git config --replace-all user.name elite-hacker
git config --replace-all user.email hacker@theboss.com
</code></pre>
<p>ואז הכניסו קומיט למערכת. יכול להיות גם ריק, והפעילו git log. מה הפרטים שאתם רואים בקומיט?</p>
<p>ברירת המחדל של גיט במצב כזה היא לתת לכם את השליטה ולכן השם שיופיע בקומיט הוא אכן השם שבחרתם. אם עכשיו תדחפו את הקומיט למאגר מרוחק הקומיט יופיע שם עם השם המזויף שלכם.</p>
<p>הדרך של גיט לאמת את הזהות שלכם בקומיט היא חתימה על קומיטים. כאן יש מדריך איך לקנפג הכל בגיטהאב:
<a target="_blank" href="https://gist.github.com/troyfontaine/18c9146295168ee9ca2b30c00bd1b41e">https://gist.github.com/troyfontaine/18c9146295168ee9ca2b30c00bd1b41e</a></p>
<p>אז למה אנחנו לא חותמים על קומיטים? (בפרויקטים פנימיים. בפרויקטי קוד פתוח הכל חתום אל דאגה). פה אני חושב שהאשמה על מנטליות ה"למי אכפת" ובפרט המחשבות:</p>
<ol>
<li><p>מי ירצה לעשות קומיט בשם אחר? כולנו עובדים באותו ארגון. אין אצלנו אנשים רעים.</p></li>
<li><p>זה בטח מסובך להגדיר את כל הדברים, ואז יהיה איזה עדכון ופתאום דברים לא יעבדו אצלי. חבל על הזמן.</p></li>
<li><p>האקרים ממילא יודעים לזייף את החתימות שלנו אז בשביל מה להתאמץ.</p></li>
</ol>
<p>אבל האמת היא שאנחנו פשוט לא אוהבים שמישהו מזיז את הגבינה שלנו. "כל החיים לא חתמתי על קומיטים ולא קרה כלום, אז למה להתחיל היום?". התשובה שלי לשלושת הסעיפים תהיה:</p>
<ol>
<li><p>אי אפשר לדעת מי ירצה לעשות קומיט בשם אחר ובאיזה נסיבות, אולי זה אפילו יקרה בטעות. תמיד עדיף להיות קצת יותר מוגן.</p></li>
<li><p>דווקא די פשוט. המדריך בלינק שהדבקתי למעלה.</p></li>
<li><p>אז למה לעשות להם חיים קלים? שיתאמצו קצת ההאקרים.</p></li>
</ol>
<p>עוד לא חותמים על קומיטים? היום הוא יום מצוין לקנפג את זה.</p>
<p>בואו נתחיל בניסוי (רק לאמיצים שבחבורה) - משורת הפקודה שנו את שמכם ואת כתובת הדואר האלקטרוני שגיט חושב שיש לכם עם הפקודות:</p>
<pre><code class="language-sh">git config --replace-all user.name elite-hacker
git config --replace-all user.email hacker@theboss.com
</code></pre>
<p>ואז הכניסו קומיט למערכת. יכול להיות גם ריק, והפעילו git log. מה הפרטים שאתם רואים בקומיט?</p>
<p>ברירת המחדל של גיט במצב כזה היא לתת לכם את השליטה ולכן השם שיופיע בקומיט הוא אכן השם שבחרתם. אם עכשיו תדחפו את הקומיט למאגר מרוחק הקומיט יופיע שם עם השם המזויף שלכם.</p>
<p>הדרך של גיט לאמת את הזהות שלכם בקומיט היא חתימה על קומיטים. כאן יש מדריך איך לקנפג הכל בגיטהאב:
<a target="_blank" href="https://gist.github.com/troyfontaine/18c9146295168ee9ca2b30c00bd1b41e">https://gist.github.com/troyfontaine/18c9146295168ee9ca2b30c00bd1b41e</a></p>
<p>אז למה אנחנו לא חותמים על קומיטים? (בפרויקטים פנימיים. בפרויקטי קוד פתוח הכל חתום אל דאגה). פה אני חושב שהאשמה על מנטליות ה"למי אכפת" ובפרט המחשבות:</p>
<ol>
<li><p>מי ירצה לעשות קומיט בשם אחר? כולנו עובדים באותו ארגון. אין אצלנו אנשים רעים.</p></li>
<li><p>זה בטח מסובך להגדיר את כל הדברים, ואז יהיה איזה עדכון ופתאום דברים לא יעבדו אצלי. חבל על הזמן.</p></li>
<li><p>האקרים ממילא יודעים לזייף את החתימות שלנו אז בשביל מה להתאמץ.</p></li>
</ol>
<p>אבל האמת היא שאנחנו פשוט לא אוהבים שמישהו מזיז את הגבינה שלנו. "כל החיים לא חתמתי על קומיטים ולא קרה כלום, אז למה להתחיל היום?". התשובה שלי לשלושת הסעיפים תהיה:</p>
<ol>
<li><p>אי אפשר לדעת מי ירצה לעשות קומיט בשם אחר ובאיזה נסיבות, אולי זה אפילו יקרה בטעות. תמיד עדיף להיות קצת יותר מוגן.</p></li>
<li><p>דווקא די פשוט. המדריך בלינק שהדבקתי למעלה.</p></li>
<li><p>אז למה לעשות להם חיים קלים? שיתאמצו קצת ההאקרים.</p></li>
</ol>
<p>עוד לא חותמים על קומיטים? היום הוא יום מצוין לקנפג את זה.</p>
tag:www.tocode.co.il,2005:BlogPost/25752024-03-09T15:00:07+02:002024-03-09T15:00:07+02:00טיפוס של GUIינון פרקSun, 10 Mar 2024 06:00:00 +0200<p>בבניית מערכת צריך להתחיל מהממשק הגרפי או מבסיס הנתונים?</p>
<p>את הארכיטקטורה צריך לכתוב מלמעלה למטה (קודם האבסטרקציות ואחרי זה הקוד עצמו) או מלמטה למעלה (קודם הקוד וכשרואים חלקים משותפים מייצרים אבסטרקציה)? </p>
<p>את הבדיקות כותבים לפני או אחרי הקוד?</p>
<p>כבר לפני הרבה שנים הבנתי שאין תשובה אחת שמתאימה לכולם וזה בסדר - אבל בגלל שרוב האנשים שקראתי היו עקביים בדעות שלהם חשבתי בטעות שזה עניין של טעם אישי, כלומר אתה יכול להיות בצד של TDD או בצד של הבדיקות אחרי הקוד. אתה יכול להיות בצד שכותב רק בדיקות אינטגרציה (וחושב שבדיקות יחידה הן בזבוז זמן) או שאתה מאמין בבדיקות יחידה וכותב אותן על כל מודול. אתה יכול להיות טיפוס שכותב GUI קודם או טיפוס שכותב קודם את קוד צד השרת.</p>
<p>אני טיפוס של GUI הייתי חושב בלב וממשיך לכתוב את הממשק לפרויקט הבא. עד שיום אחד זה כבר לא עבד. שמכל מיני סיבות ואילוצים של הפרויקט כל פעם שניסיתי להתחיל מה GUI הקוד לא כתב את עצמו בכלל והגענו למבוי סתום. היפוך הכיוון עזר לקדם את הפרויקט אבל יותר מזה עזר לי לצאת מהקיבעון.</p>
<p>אני יכול להיות טיפוס של GUI או של Back End, אבל זה בכלל לא רלוונטי. יותר חשוב לשאול מה האילוצים של הפרויקט? מה מידת הגמישות שנצטרך בו? מה אנחנו יודעים לעומת מה עוד צריך לגלות? איזה חלקים צפויים להשתנות? ואולי בכלל אפשר לחלק את העבודה ולהתקדם במקביל?</p>
<p>שורה תחתונה השאלה היא לא איפה אנחנו מרגישים יותר נוח אלא מה טוב לפרויקט ומה יאפשר לנו יותר גמישות כשנצטרך אותה. מתכנתים טובים צריכים להיות מסוגלים לעבור בין הגישות ולהעדיף תמיד את טובת הפרויקט.</p>
<p>בבניית מערכת צריך להתחיל מהממשק הגרפי או מבסיס הנתונים?</p>
<p>את הארכיטקטורה צריך לכתוב מלמעלה למטה (קודם האבסטרקציות ואחרי זה הקוד עצמו) או מלמטה למעלה (קודם הקוד וכשרואים חלקים משותפים מייצרים אבסטרקציה)? </p>
<p>את הבדיקות כותבים לפני או אחרי הקוד?</p>
<p>כבר לפני הרבה שנים הבנתי שאין תשובה אחת שמתאימה לכולם וזה בסדר - אבל בגלל שרוב האנשים שקראתי היו עקביים בדעות שלהם חשבתי בטעות שזה עניין של טעם אישי, כלומר אתה יכול להיות בצד של TDD או בצד של הבדיקות אחרי הקוד. אתה יכול להיות בצד שכותב רק בדיקות אינטגרציה (וחושב שבדיקות יחידה הן בזבוז זמן) או שאתה מאמין בבדיקות יחידה וכותב אותן על כל מודול. אתה יכול להיות טיפוס שכותב GUI קודם או טיפוס שכותב קודם את קוד צד השרת.</p>
<p>אני טיפוס של GUI הייתי חושב בלב וממשיך לכתוב את הממשק לפרויקט הבא. עד שיום אחד זה כבר לא עבד. שמכל מיני סיבות ואילוצים של הפרויקט כל פעם שניסיתי להתחיל מה GUI הקוד לא כתב את עצמו בכלל והגענו למבוי סתום. היפוך הכיוון עזר לקדם את הפרויקט אבל יותר מזה עזר לי לצאת מהקיבעון.</p>
<p>אני יכול להיות טיפוס של GUI או של Back End, אבל זה בכלל לא רלוונטי. יותר חשוב לשאול מה האילוצים של הפרויקט? מה מידת הגמישות שנצטרך בו? מה אנחנו יודעים לעומת מה עוד צריך לגלות? איזה חלקים צפויים להשתנות? ואולי בכלל אפשר לחלק את העבודה ולהתקדם במקביל?</p>
<p>שורה תחתונה השאלה היא לא איפה אנחנו מרגישים יותר נוח אלא מה טוב לפרויקט ומה יאפשר לנו יותר גמישות כשנצטרך אותה. מתכנתים טובים צריכים להיות מסוגלים לעבור בין הגישות ולהעדיף תמיד את טובת הפרויקט.</p>
tag:www.tocode.co.il,2005:BlogPost/25742024-03-08T22:00:07+02:002024-03-08T22:00:07+02:00שלוש נקודות על SQL-ים מסובכים מדיינון פרקSat, 09 Mar 2024 06:00:00 +0200<p>התוכנית המקורית שלי לפוסט סופשבוע היתה לפתור ולפרסם עוד יום של Advent Of Code, אבל מפה לשם את כל זמן הכתיבה בזבזתי על שאילתה מסובכת מדי לבסיס הנתונים שבסוף גם היא לא עבדה, ולכן במקום אני רוצה לשתף שלוש נקודות אליהן כדאי לשים לב כשהולכים מכות עם שאילתות (ובעצם עם פריימוורק באופן כללי)-</p>
<ol>
<li><p>לפעמים זה שווה את המאמץ. יש שאילתות SQL שאחרי שמצליחים לכתוב אותן מבינים משהו חדש על SQL. כן יש דבר כזה <a target="_blank" href="https://mode.com/sql-tutorial/sql-window-functions">Window Functions</a> ואם צריך להשקיע עכשיו שעתיים בללמוד איך הן עובדות זה ממש שווה את ההשקעה.</p></li>
<li><p>לפעמים השאילתה באמת מסובכת מדי ולא שווה את המאמץ. ראינו כבר מספיק שאילתות מסובכות מדי שבסוף היה צריך לשכתב לשאילתות פשוטות יותר או להעביר חלק מהלוגיקה פנימה לקוד כי ה DB לא עמד בעומס.</p></li>
<li><p>לפעמים שאילתות מסובכות הן רמז לבעייה במבנה הטבלאות.</p></li>
</ol>
<p>באותו רגע כשמנסים לגרום לשאילתה לעבוד אנחנו עדיין לא יודעים איזה משלושת הדרכים תהיה הטובה ביותר וקל להיתקע על הפיתרון הראשון שנראה הגיוני. בואו ננסה לזכור שתמיד יש עוד דרכים ואולי הפיתרון הוא צעד אחורה והמשך בכיוון אחר.</p>
<p>התוכנית המקורית שלי לפוסט סופשבוע היתה לפתור ולפרסם עוד יום של Advent Of Code, אבל מפה לשם את כל זמן הכתיבה בזבזתי על שאילתה מסובכת מדי לבסיס הנתונים שבסוף גם היא לא עבדה, ולכן במקום אני רוצה לשתף שלוש נקודות אליהן כדאי לשים לב כשהולכים מכות עם שאילתות (ובעצם עם פריימוורק באופן כללי)-</p>
<ol>
<li><p>לפעמים זה שווה את המאמץ. יש שאילתות SQL שאחרי שמצליחים לכתוב אותן מבינים משהו חדש על SQL. כן יש דבר כזה <a target="_blank" href="https://mode.com/sql-tutorial/sql-window-functions">Window Functions</a> ואם צריך להשקיע עכשיו שעתיים בללמוד איך הן עובדות זה ממש שווה את ההשקעה.</p></li>
<li><p>לפעמים השאילתה באמת מסובכת מדי ולא שווה את המאמץ. ראינו כבר מספיק שאילתות מסובכות מדי שבסוף היה צריך לשכתב לשאילתות פשוטות יותר או להעביר חלק מהלוגיקה פנימה לקוד כי ה DB לא עמד בעומס.</p></li>
<li><p>לפעמים שאילתות מסובכות הן רמז לבעייה במבנה הטבלאות.</p></li>
</ol>
<p>באותו רגע כשמנסים לגרום לשאילתה לעבוד אנחנו עדיין לא יודעים איזה משלושת הדרכים תהיה הטובה ביותר וקל להיתקע על הפיתרון הראשון שנראה הגיוני. בואו ננסה לזכור שתמיד יש עוד דרכים ואולי הפיתרון הוא צעד אחורה והמשך בכיוון אחר.</p>
tag:www.tocode.co.il,2005:BlogPost/25732024-03-07T18:00:07+02:002024-03-07T18:00:07+02:00מה עושים במקום התיעוד הלא רלוונטיינון פרקFri, 08 Mar 2024 06:00:00 +0200<p>אה זה מה Readme? תתעלם הוא לא מעודכן</p>
<p>ההערה הזאת ישנה את יכולה להתעלם, שינינו הרבה קוד מאז</p>
<p>תתעלם מהבדיקות שנכשלות צריך לסדר אותן</p>
<p>עכשיו אני יודע אין זמן לעדכן ולא נעים למחוק, אז מה עושים עם כל התיעוד הלא רלוונטי הזה? איך נדאג לזה שהוא לא ימשיך לבלבל אחרים? טריק אחד שהתחלתי לנסות זה כן למחוק את הדברים ובמקומם להשאיר הערה בקוד עם מספר הקומיט שבו אותו תיעוד ישן כן היה במערכת. ככה מי שיגיע למקום יצטרך לעבוד כדי למצוא את אותו תיעוד לא רלוונטי, וגם יהיה לכולם ברור מה מצבו של אותו תיעוד. כשמישהו כן ימצא את הזמן לתקן יהיה קל למצוא את הגירסה האחרונה ולהמשיך משם.</p>
<p>אה זה מה Readme? תתעלם הוא לא מעודכן</p>
<p>ההערה הזאת ישנה את יכולה להתעלם, שינינו הרבה קוד מאז</p>
<p>תתעלם מהבדיקות שנכשלות צריך לסדר אותן</p>
<p>עכשיו אני יודע אין זמן לעדכן ולא נעים למחוק, אז מה עושים עם כל התיעוד הלא רלוונטי הזה? איך נדאג לזה שהוא לא ימשיך לבלבל אחרים? טריק אחד שהתחלתי לנסות זה כן למחוק את הדברים ובמקומם להשאיר הערה בקוד עם מספר הקומיט שבו אותו תיעוד ישן כן היה במערכת. ככה מי שיגיע למקום יצטרך לעבוד כדי למצוא את אותו תיעוד לא רלוונטי, וגם יהיה לכולם ברור מה מצבו של אותו תיעוד. כשמישהו כן ימצא את הזמן לתקן יהיה קל למצוא את הגירסה האחרונה ולהמשיך משם.</p>
tag:www.tocode.co.il,2005:BlogPost/25722024-03-06T18:00:09+02:002024-03-06T18:00:09+02:00קונספציותינון פרקThu, 07 Mar 2024 06:00:00 +0200<p>שני סיפורים לא קשורים על פערי ציפיות שאכלו לי יותר מדי זמן השבוע (בטח מספיק בשביל להופיע פה בבלוג).</p>
<p>הראשון הוא השילוב בין דוקר לחומת האש ufw. במשפט שני אלה לא עובדים טוב יחד. כשמפעילים ufw כדי לחסום חיבורים נכנסים הוא לא חוסם חיבורים שממופים לקונטיינרים בדוקר. במילים אחרות אם הפעלתם קונטיינר שמקשיב על פורט 8080:</p>
<pre><code class="language-sh">docker run -dit --name my-apache-app -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4
</code></pre>
<p>ואז תפעילו ufw כדי לחסום חיבורים מבחוץ לפורט 8080, החיבורים עדיין ייכנסו ויגיעו לקונטיינר.</p>
<p>אני מודה כשראיתי את ההתנהגות הזאת לא האמנתי. הייתי בטוח שאני עושה משהו לא בסדר במקום אחר. אחרי כמה שעות הלכתי לגוגל ומצאתי שהסיפור מתועד למשל כאן:
<a target="_blank" href="https://blog.jarrousse.org/2023/03/18/how-to-use-ufw-firewall-with-docker-containers/">https://blog.jarrousse.org/2023/03/18/how-to-use-ufw-firewall-with-docker-containers/</a></p>
<p>וכאן:
<a target="_blank" href="https://www.baeldung.com/linux/docker-container-published-port-ignoring-ufw-rules">https://www.baeldung.com/linux/docker-container-published-port-ignoring-ufw-rules</a></p>
<p>וכאן:
<a target="_blank" href="https://www.howtogeek.com/devops/how-to-use-docker-with-a-ufw-firewall/">https://www.howtogeek.com/devops/how-to-use-docker-with-a-ufw-firewall/</a></p>
<p>הסיפור השני הוא על JanusGraph ויצירת אינדקס ייחודי בגרף. בעמוד התיעוד הראשי הם משתפים את הפקודה שיוצרת אינדקס ייחודי:</p>
<pre><code class="language-sh">index = mgmt.buildIndex('byConsistentName', Vertex.class).addKey(name).unique().buildCompositeIndex()
</code></pre>
<p>רק מה, זה לא עובד. בשביל ליצור אינדקס ייחודי שגם מוודא שדברים נוצרים בצורה ייחודית צריך לבקש ממנו להשתמש גם במנגנון הנעילה או במילים אחרות:</p>
<pre><code class="language-sh">index = mgmt.buildIndex('byConsistentName', Vertex.class).addKey(name).unique().buildCompositeIndex()
mgmt.setConsistency(index, ConsistencyModifier.LOCK)
</code></pre>
<p>וגם זה מתועד יפה כשיודעים איפה לחפש:
<a target="_blank" href="https://docs.janusgraph.org/advanced-topics/eventual-consistency/">https://docs.janusgraph.org/advanced-topics/eventual-consistency/</a></p>
<p>גם אם שני הסיפורים הם על שתי טכנולוגיות שונות יש קשר חזק ביניהם. בשני המקרים חוסר תשומת לב יצרה בעיות שבהמשך הרבה יותר קשה לתקן. בכתיבת קוד שימו לב להשתמש תמיד במנה גדושה של ספקנות בריאה ותמיד לבחון את ההנחות שלכם, גם כשדברים נראים מובנים מאליהם.</p>
<p>שני סיפורים לא קשורים על פערי ציפיות שאכלו לי יותר מדי זמן השבוע (בטח מספיק בשביל להופיע פה בבלוג).</p>
<p>הראשון הוא השילוב בין דוקר לחומת האש ufw. במשפט שני אלה לא עובדים טוב יחד. כשמפעילים ufw כדי לחסום חיבורים נכנסים הוא לא חוסם חיבורים שממופים לקונטיינרים בדוקר. במילים אחרות אם הפעלתם קונטיינר שמקשיב על פורט 8080:</p>
<pre><code class="language-sh">docker run -dit --name my-apache-app -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4
</code></pre>
<p>ואז תפעילו ufw כדי לחסום חיבורים מבחוץ לפורט 8080, החיבורים עדיין ייכנסו ויגיעו לקונטיינר.</p>
<p>אני מודה כשראיתי את ההתנהגות הזאת לא האמנתי. הייתי בטוח שאני עושה משהו לא בסדר במקום אחר. אחרי כמה שעות הלכתי לגוגל ומצאתי שהסיפור מתועד למשל כאן:
<a target="_blank" href="https://blog.jarrousse.org/2023/03/18/how-to-use-ufw-firewall-with-docker-containers/">https://blog.jarrousse.org/2023/03/18/how-to-use-ufw-firewall-with-docker-containers/</a></p>
<p>וכאן:
<a target="_blank" href="https://www.baeldung.com/linux/docker-container-published-port-ignoring-ufw-rules">https://www.baeldung.com/linux/docker-container-published-port-ignoring-ufw-rules</a></p>
<p>וכאן:
<a target="_blank" href="https://www.howtogeek.com/devops/how-to-use-docker-with-a-ufw-firewall/">https://www.howtogeek.com/devops/how-to-use-docker-with-a-ufw-firewall/</a></p>
<p>הסיפור השני הוא על JanusGraph ויצירת אינדקס ייחודי בגרף. בעמוד התיעוד הראשי הם משתפים את הפקודה שיוצרת אינדקס ייחודי:</p>
<pre><code class="language-sh">index = mgmt.buildIndex('byConsistentName', Vertex.class).addKey(name).unique().buildCompositeIndex()
</code></pre>
<p>רק מה, זה לא עובד. בשביל ליצור אינדקס ייחודי שגם מוודא שדברים נוצרים בצורה ייחודית צריך לבקש ממנו להשתמש גם במנגנון הנעילה או במילים אחרות:</p>
<pre><code class="language-sh">index = mgmt.buildIndex('byConsistentName', Vertex.class).addKey(name).unique().buildCompositeIndex()
mgmt.setConsistency(index, ConsistencyModifier.LOCK)
</code></pre>
<p>וגם זה מתועד יפה כשיודעים איפה לחפש:
<a target="_blank" href="https://docs.janusgraph.org/advanced-topics/eventual-consistency/">https://docs.janusgraph.org/advanced-topics/eventual-consistency/</a></p>
<p>גם אם שני הסיפורים הם על שתי טכנולוגיות שונות יש קשר חזק ביניהם. בשני המקרים חוסר תשומת לב יצרה בעיות שבהמשך הרבה יותר קשה לתקן. בכתיבת קוד שימו לב להשתמש תמיד במנה גדושה של ספקנות בריאה ותמיד לבחון את ההנחות שלכם, גם כשדברים נראים מובנים מאליהם.</p>
tag:www.tocode.co.il,2005:BlogPost/25712024-03-05T09:00:08+02:002024-03-05T09:00:08+02:00היום למדתי: סטיב ווזניאק לא משעמםינון פרקWed, 06 Mar 2024 06:00:00 +0200<p>במהלך העבודה עם דוקר אי אפשר להתעלם משמות הקונטיינרים היצירתיים שלו - <code dir="ltr">elegant_ride</code>, <code dir="ltr">nervous_jang</code>, <code dir="ltr">relaxed_vaughan</code>, <code dir="ltr">interesting_wiles</code> ועוד עשרות צירופים בסגנון זה. לא נעים להודות שלא ייחסתי חשיבות גדולה לבחירת השמות כי הייתי עסוק תמיד בלגרום לאיזה תוכנית לעבוד.</p>
<p>היום למדתי שהיתה דווקא הרבה מחשבה מאחורי השמות. הרשימה שלמעלה כוללת התיחסות לסאלי רייד, האמריקאית הראשונה שהגיעה לחלל, דורותי ווהן שהיתה מתמטיקאית חלוצה בנאסא, יאנג יונג-סיל שהיה מהנדס בקוריאה במאה ה 15 ואנדרו ויילס, מתמטיקאי בריטי שהוכיח את השערת טניאמה-שימורה.</p>
<p>קוד הגרלת השמות של דוקר כולל את רשימת כל המדענים והחוקרים שהם יכולים להציע עם הסבר קצר על כל אחד ואתם יכולים גם למצוא אותו בקישור:
<a target="_blank" href="https://github.com/moby/moby/blob/39f7b2b6d0156811d9683c6cb0743118ae516a11/pkg/namesgenerator/names-generator.go#L852-L863">https://github.com/moby/moby/blob/39f7b2b6d0156811d9683c6cb0743118ae516a11/pkg/namesgenerator/names-generator.go#L852-L863</a></p>
<p>ופונקציית ההגרלה עצמה אגב פשוטה להפליא וכוללת טיפול רק במקרה קצה אחד:</p>
<pre><code class="language-go">func GetRandomName(retry int) string {
begin:
name := left[rand.Intn(len(left))] + "_" + right[rand.Intn(len(right))] //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
goto begin
}
if retry > 0 {
name += strconv.Itoa(rand.Intn(10)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
}
return name
}
</code></pre>
<p>במהלך העבודה עם דוקר אי אפשר להתעלם משמות הקונטיינרים היצירתיים שלו - <code dir="ltr">elegant_ride</code>, <code dir="ltr">nervous_jang</code>, <code dir="ltr">relaxed_vaughan</code>, <code dir="ltr">interesting_wiles</code> ועוד עשרות צירופים בסגנון זה. לא נעים להודות שלא ייחסתי חשיבות גדולה לבחירת השמות כי הייתי עסוק תמיד בלגרום לאיזה תוכנית לעבוד.</p>
<p>היום למדתי שהיתה דווקא הרבה מחשבה מאחורי השמות. הרשימה שלמעלה כוללת התיחסות לסאלי רייד, האמריקאית הראשונה שהגיעה לחלל, דורותי ווהן שהיתה מתמטיקאית חלוצה בנאסא, יאנג יונג-סיל שהיה מהנדס בקוריאה במאה ה 15 ואנדרו ויילס, מתמטיקאי בריטי שהוכיח את השערת טניאמה-שימורה.</p>
<p>קוד הגרלת השמות של דוקר כולל את רשימת כל המדענים והחוקרים שהם יכולים להציע עם הסבר קצר על כל אחד ואתם יכולים גם למצוא אותו בקישור:
<a target="_blank" href="https://github.com/moby/moby/blob/39f7b2b6d0156811d9683c6cb0743118ae516a11/pkg/namesgenerator/names-generator.go#L852-L863">https://github.com/moby/moby/blob/39f7b2b6d0156811d9683c6cb0743118ae516a11/pkg/namesgenerator/names-generator.go#L852-L863</a></p>
<p>ופונקציית ההגרלה עצמה אגב פשוטה להפליא וכוללת טיפול רק במקרה קצה אחד:</p>
<pre><code class="language-go">func GetRandomName(retry int) string {
begin:
name := left[rand.Intn(len(left))] + "_" + right[rand.Intn(len(right))] //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
goto begin
}
if retry > 0 {
name += strconv.Itoa(rand.Intn(10)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
}
return name
}
</code></pre>
tag:www.tocode.co.il,2005:BlogPost/25702024-03-04T18:00:07+02:002024-03-04T18:00:07+02:00כמה זה באמת עולה?ינון פרקTue, 05 Mar 2024 06:00:00 +0200<p>המחיר האמיתי של בעיה כולל לא רק את הערך שאנחנו מפסידים בזה שלא פותרים אותה, אלא גם את כל הזמן האבוד שאנחנו שורפים על ניסיונות נוספים להתמודד.</p>
<p>שרת יציב שמחובר למערכת ניטור שגיאות יאפשר לי לראות מיד כשיש בעיה וגם לחפור במדדים של השרת כדי להבין מה נשבר. אבל אם השרת לא בנוי כמו שצריך גם יהיו בו יותר בעיות, וגם כשכבר יש בעיה אין לי דרך טובה לחקור אותה ולתקן את הדבר האמיתי ששבור, ואז צריך לשרוף שעות בניסיונות עד שמגיעים למשהו.</p>
<p>רוב הזמן אנחנו יכולים לראות את הבעיות שעוצרות אותנו, אבל אין לנו זמן או ידע מתאים כדי לפתור אותן או שיש בעיות שנראות יותר דחופות. ולכן האתגר שלנו (כמפתחים, כיזמים, כמנהלים) הוא לזהות את העלות האמיתית של בעיה. בעיה שעולה יום עבודה פעם בחודש כל חודש היא דחופה, גם אם היום הוא לא היום של השריפה.</p>
<p>המחיר האמיתי של בעיה כולל לא רק את הערך שאנחנו מפסידים בזה שלא פותרים אותה, אלא גם את כל הזמן האבוד שאנחנו שורפים על ניסיונות נוספים להתמודד.</p>
<p>שרת יציב שמחובר למערכת ניטור שגיאות יאפשר לי לראות מיד כשיש בעיה וגם לחפור במדדים של השרת כדי להבין מה נשבר. אבל אם השרת לא בנוי כמו שצריך גם יהיו בו יותר בעיות, וגם כשכבר יש בעיה אין לי דרך טובה לחקור אותה ולתקן את הדבר האמיתי ששבור, ואז צריך לשרוף שעות בניסיונות עד שמגיעים למשהו.</p>
<p>רוב הזמן אנחנו יכולים לראות את הבעיות שעוצרות אותנו, אבל אין לנו זמן או ידע מתאים כדי לפתור אותן או שיש בעיות שנראות יותר דחופות. ולכן האתגר שלנו (כמפתחים, כיזמים, כמנהלים) הוא לזהות את העלות האמיתית של בעיה. בעיה שעולה יום עבודה פעם בחודש כל חודש היא דחופה, גם אם היום הוא לא היום של השריפה.</p>
tag:www.tocode.co.il,2005:BlogPost/25692024-03-03T17:00:07+02:002024-03-03T17:00:07+02:00טיפ ריאקט: עדיף לוותר על טרנרי בתוך JSXינון פרקMon, 04 Mar 2024 06:00:00 +0200<p>יותר מדי פעמים אני רואה קוד עם כוונות טובות שלא עומד במבחן הזמן. במקרה של ריאקט תבנית מאוד בעייתית היא הכנסת לוגיקה לתוך ה JSX. זה מתחיל פשוט עם איזה סימן שאלה נקודותיים:</p>
<pre><code class="language-javascript">function App() {
const [text, setText] = useState(0);
return (
<div>
<button onClick={() => setText(t => (t + 1) % 2)}>Toggle</button>
{text == 0 ? <Text1 /> : <Text2 />}
</div>
)
}
</code></pre>
<p>אבל מהר מאוד הופך למפלצת.</p>
<p>מה עושים במקום? מנגנון קל הוא להוציא את הקוד המכוער לקומפוננטה נפרדת שחיה רק בשביל הלוגיקה הזאת. החלטנו שצריך להתלבט איזה קומפוננטה להציג? בואו נעביר את זה לקומפוננטה נפרדת ונקבל:</p>
<pre><code class="language-javascript">function Text({id}) {
if (id === 0) {
return <Text1 />
} else {
return <Text2 />
}
}
function App() {
const [text, setText] = useState(0);
return (
<div>
<button onClick={() => setText(t => (t + 1) % 2)}>Toggle</button>
<Text id={text} />
</div>
)
}
</code></pre>
<p>בשלב הבא אם נצטרך מנגנון יותר גנרי (למשל כי נצטרך שוב לבחור בין כמה קומפוננטות) נוכל להפוך את Text לפונקציה כללית ונקבל:</p>
<pre><code class="language-javascript">function Text1({color}) {
return <p style={{color}}>Text 1</p>
}
function Text2({color}) {
return <p style={{color}}>Text 2</p>
}
const Toggle = (...components) => (props) => {
const cls = components[props.id]
return React.createElement(cls, props);
}
const Text = Toggle(Text1, Text2);
function App() {
const [text, setText] = useState(0);
return (
<div>
<button onClick={() => setText(t => (t + 1) % 2)}>Toggle</button>
<Text id={text} color="red" />
</div>
)
}
</code></pre>
<p>יותר מדי פעמים אני רואה קוד עם כוונות טובות שלא עומד במבחן הזמן. במקרה של ריאקט תבנית מאוד בעייתית היא הכנסת לוגיקה לתוך ה JSX. זה מתחיל פשוט עם איזה סימן שאלה נקודותיים:</p>
<pre><code class="language-javascript">function App() {
const [text, setText] = useState(0);
return (
<div>
<button onClick={() => setText(t => (t + 1) % 2)}>Toggle</button>
{text == 0 ? <Text1 /> : <Text2 />}
</div>
)
}
</code></pre>
<p>אבל מהר מאוד הופך למפלצת.</p>
<p>מה עושים במקום? מנגנון קל הוא להוציא את הקוד המכוער לקומפוננטה נפרדת שחיה רק בשביל הלוגיקה הזאת. החלטנו שצריך להתלבט איזה קומפוננטה להציג? בואו נעביר את זה לקומפוננטה נפרדת ונקבל:</p>
<pre><code class="language-javascript">function Text({id}) {
if (id === 0) {
return <Text1 />
} else {
return <Text2 />
}
}
function App() {
const [text, setText] = useState(0);
return (
<div>
<button onClick={() => setText(t => (t + 1) % 2)}>Toggle</button>
<Text id={text} />
</div>
)
}
</code></pre>
<p>בשלב הבא אם נצטרך מנגנון יותר גנרי (למשל כי נצטרך שוב לבחור בין כמה קומפוננטות) נוכל להפוך את Text לפונקציה כללית ונקבל:</p>
<pre><code class="language-javascript">function Text1({color}) {
return <p style={{color}}>Text 1</p>
}
function Text2({color}) {
return <p style={{color}}>Text 2</p>
}
const Toggle = (...components) => (props) => {
const cls = components[props.id]
return React.createElement(cls, props);
}
const Text = Toggle(Text1, Text2);
function App() {
const [text, setText] = useState(0);
return (
<div>
<button onClick={() => setText(t => (t + 1) % 2)}>Toggle</button>
<Text id={text} color="red" />
</div>
)
}
</code></pre>
tag:www.tocode.co.il,2005:BlogPost/25682024-03-02T21:00:10+02:002024-03-02T21:00:10+02:00חלומות על PGliteינון פרקSun, 03 Mar 2024 06:00:00 +0200<p>הפעלתי היום קוד טייפסקריפט שהתחבר לבסיס נתונים <a target="_blank" href="https://github.com/electric-sql/pglite">PGlite</a>, יצר טבלה, הכניס נתונים ושלף אותם חזרה. החלק המלהיב הוא שלא היה לי בסיס נתונים מותקן. זה הקוד וצריך רק deno run בשביל להריץ:</p>
<pre><code class="language-typescript">import { PGlite } from "npm:@electric-sql/pglite"
async function main() {
const db = new PGlite()
await db.query("create table test(x integer, y integer);");
await db.query("insert into test values(1, 1);");
await db.query("insert into test values(2, 2);");
const result = await db.query("select * from test");
console.log(result);
}
main();
</code></pre>
<p>הפרויקט עדיין בשלבי פיתוח וידרוש עוד עבודה עד שנקבל תמיכה ב ORM-ים אבל כבר אפשר לחלום על כמה שיפורים משמעותיים בפיתוח שהוא יוכל להכניס לאקוסיסטם של JavaScript בצד שרת-</p>
<ol>
<li><p>מהירות - כי אם בסיס הנתונים רץ באותו תהליך כמו השרת אז יותר זול להוציא קריאות לבסיס הנתונים.</p></li>
<li><p>שאילתות יותר פשוטות - המשך של הסעיף הקודם, כי אם בסיס הנתונים רץ אצלי באותו תהליך אני יכול להוציא גם 20 שאילתות ויכול לוותר על JOIN-ים או חיבורים מורכבים בין טבלאות.</p></li>
<li><p>בדיקות מהירות ומקביליות - כי עכשיו כל בדיקה יכולה ליצור לעצמה את בסיס הנתונים בזיכרון מתוך נתוני Seed קבועים.</p></li>
<li><p>פורטינג יותר מהיר לאפליקציות Offline First - כי אפשר לקחת את אותן שאילתות פוסטגרס שכבר יש לנו ופשוט להריץ הכל בדפדפן.</p></li>
</ol>
<p>כמה מהדברים האלה יעבדו ומתי עדיין מוקדם לנחש. כנראה שפורטינג ל Offline First יהיה הדבר הראשון, אחרי זה נתחיל להשתמש ב PGlite בתשתית הבדיקות עד שיהיה מספיק יציב להריץ את כל השאילתות של המערכת האמיתית ולאט לאט יהיו אמיצים שיבחרו אותו בתור בסיס נתונים מלא. או שלא. בכל מקרה זה פרויקט ששווה לעקוב אחריו.</p>
<p>הפעלתי היום קוד טייפסקריפט שהתחבר לבסיס נתונים <a target="_blank" href="https://github.com/electric-sql/pglite">PGlite</a>, יצר טבלה, הכניס נתונים ושלף אותם חזרה. החלק המלהיב הוא שלא היה לי בסיס נתונים מותקן. זה הקוד וצריך רק deno run בשביל להריץ:</p>
<pre><code class="language-typescript">import { PGlite } from "npm:@electric-sql/pglite"
async function main() {
const db = new PGlite()
await db.query("create table test(x integer, y integer);");
await db.query("insert into test values(1, 1);");
await db.query("insert into test values(2, 2);");
const result = await db.query("select * from test");
console.log(result);
}
main();
</code></pre>
<p>הפרויקט עדיין בשלבי פיתוח וידרוש עוד עבודה עד שנקבל תמיכה ב ORM-ים אבל כבר אפשר לחלום על כמה שיפורים משמעותיים בפיתוח שהוא יוכל להכניס לאקוסיסטם של JavaScript בצד שרת-</p>
<ol>
<li><p>מהירות - כי אם בסיס הנתונים רץ באותו תהליך כמו השרת אז יותר זול להוציא קריאות לבסיס הנתונים.</p></li>
<li><p>שאילתות יותר פשוטות - המשך של הסעיף הקודם, כי אם בסיס הנתונים רץ אצלי באותו תהליך אני יכול להוציא גם 20 שאילתות ויכול לוותר על JOIN-ים או חיבורים מורכבים בין טבלאות.</p></li>
<li><p>בדיקות מהירות ומקביליות - כי עכשיו כל בדיקה יכולה ליצור לעצמה את בסיס הנתונים בזיכרון מתוך נתוני Seed קבועים.</p></li>
<li><p>פורטינג יותר מהיר לאפליקציות Offline First - כי אפשר לקחת את אותן שאילתות פוסטגרס שכבר יש לנו ופשוט להריץ הכל בדפדפן.</p></li>
</ol>
<p>כמה מהדברים האלה יעבדו ומתי עדיין מוקדם לנחש. כנראה שפורטינג ל Offline First יהיה הדבר הראשון, אחרי זה נתחיל להשתמש ב PGlite בתשתית הבדיקות עד שיהיה מספיק יציב להריץ את כל השאילתות של המערכת האמיתית ולאט לאט יהיו אמיצים שיבחרו אותו בתור בסיס נתונים מלא. או שלא. בכל מקרה זה פרויקט ששווה לעקוב אחריו.</p>
tag:www.tocode.co.il,2005:BlogPost/25672024-03-01T16:00:08+02:002024-03-01T16:00:08+02:00ממשק אחד שעושה הכלינון פרקSat, 02 Mar 2024 06:00:00 +0200<p>צריכים לכתוב שחקן מחשב לאיקס עיגול (או אולי לשחמט)?</p>
<p>ליצור תוכנית אימונים מותאמת אישית לחדר כושר, לפי רשימת אילוצים ורצונות של משתמש?</p>
<p>לזהות באיזה שפה הקובץ? או לתרגם את הטקסט לשפה אחרת?</p>
<p>לחפש מתכונים שמתאימים לרשימת מרכיבים שיש למשתמש במקרר כרגע?</p>
<p>לפענח קוד QR בתמונה?</p>
<p>כשנצטרך לפתור כל אחת מהבעיות ברשימה למעלה (ובעיות רבות נוספות), רובנו נחפש או ננסה לחשוב על אלגוריתם - נדמיין את הקלט, הפלט ואופן החישוב. אנחנו רוצים לדעת מה הקוד עושה ולהיות מסוגלים לתקן את הפיתרון ולשפר אותו. </p>
<p>אבל אפשר כבר לדמיין עתיד מסוג אחר.</p>
<p>באותו עתיד אפשר לדמיין איך במקום לכתוב את הקוד לפיתרון הבעיה אנחנו נכתוב קוד שינגיש את הבעיה לאיזה "אורקל" כמו Chat GPT ויפענח את תשובתו. תיקוני באגים יהיו בסך הכל Prompt Engineering ובדיקות אוטומטיות יהיו רשימה של פרומפטים מוכנים מראש והתוצאות שאנחנו מצפים לקבל מהמכונה. ההזדמנות כבר שם לקצר תהליכי פיתוח בצורה משמעותית, ועם הזמן היא תיהפך יותר ברורה.</p>
<p>צריכים לכתוב שחקן מחשב לאיקס עיגול (או אולי לשחמט)?</p>
<p>ליצור תוכנית אימונים מותאמת אישית לחדר כושר, לפי רשימת אילוצים ורצונות של משתמש?</p>
<p>לזהות באיזה שפה הקובץ? או לתרגם את הטקסט לשפה אחרת?</p>
<p>לחפש מתכונים שמתאימים לרשימת מרכיבים שיש למשתמש במקרר כרגע?</p>
<p>לפענח קוד QR בתמונה?</p>
<p>כשנצטרך לפתור כל אחת מהבעיות ברשימה למעלה (ובעיות רבות נוספות), רובנו נחפש או ננסה לחשוב על אלגוריתם - נדמיין את הקלט, הפלט ואופן החישוב. אנחנו רוצים לדעת מה הקוד עושה ולהיות מסוגלים לתקן את הפיתרון ולשפר אותו. </p>
<p>אבל אפשר כבר לדמיין עתיד מסוג אחר.</p>
<p>באותו עתיד אפשר לדמיין איך במקום לכתוב את הקוד לפיתרון הבעיה אנחנו נכתוב קוד שינגיש את הבעיה לאיזה "אורקל" כמו Chat GPT ויפענח את תשובתו. תיקוני באגים יהיו בסך הכל Prompt Engineering ובדיקות אוטומטיות יהיו רשימה של פרומפטים מוכנים מראש והתוצאות שאנחנו מצפים לקבל מהמכונה. ההזדמנות כבר שם לקצר תהליכי פיתוח בצורה משמעותית, ועם הזמן היא תיהפך יותר ברורה.</p>
tag:www.tocode.co.il,2005:BlogPost/25662024-02-29T17:00:07+02:002024-02-29T17:00:07+02:00חדש באתר - סידרת useEffectינון פרקFri, 01 Mar 2024 06:00:00 +0200<p>אין ספק ש useEffect הוא ה Hook המבלבל ביותר בריאקט. מצד אחד אפשר לעשות איתו המון דברים וגם יש המון מקרים בהם באמת צריך אותו, אבל מצד שני קל מאוד להשתמש בו לא נכון ולכן רוב האנשים נתקלים בבאגים מוזרים כשמתחילים לכתוב אפקטים בקומפוננטות.</p>
<p>עוד כשכתבו לראשונה את המנגנון דן אברמוב דן עליו באריכות בבלוג שלו:
<a target="_blank" href="https://overreacted.io/a-complete-guide-to-useeffect/">https://overreacted.io/a-complete-guide-to-useeffect/</a></p>
<p>וזה עזר, אבל רק במידה מסוימת כי בסוף עד שלא מנסים את הדברים על קוד אמיתי קשה לראות את הבעיות.</p>
<p>בשנים שעברו מאז ש useEffect יצא נצבר לא מעט ידע על איך להשתמש בו נכון, למה לשים לב ומהם הבאגים הכי נפוצים שניתקל בהם. למרות שלימדתי את useEffect בקורס ריאקט השיעורים שם היו ממוקדים במה אפשר לבנות עם useEffect הרבה יותר מאשר במה אפשר לשבור איתו.</p>
<p>השבוע בעקבות כמה שאלות מתלמידים ישבתי לסדר את השיעורים האלה בקורס ולהקליט מחדש 3 סרטים על useEffect בגירסאות ארוכות ומפורטות (שעה של וידאו על useEffect אמרתם? יאללה קיבלתם). הפעם לקחתי כל דוגמה והראיתי את כל הטעויות שאפשר לעשות ואיך לזהות כל טעות.</p>
<p>אם יש לכם שעה פנויה ומנוי לאתר, ואתם כותבים ריאקט ביום יום, אני ממליץ להעיף מבט בסידרה המחודשת בקישור: <a href="https://www.tocode.co.il/bundles/react/toc">https://www.tocode.co.il/bundles/react/toc</a> שיעורים 25-27.</p>
<p>וכמו תמיד אם יש נושאים נוספים שהייתם רוצים לראות בקורסים כאן אל תתביישו לכתוב, אפשר דרך מסך צרו קשר כאן באתר.</p>
<p>אין ספק ש useEffect הוא ה Hook המבלבל ביותר בריאקט. מצד אחד אפשר לעשות איתו המון דברים וגם יש המון מקרים בהם באמת צריך אותו, אבל מצד שני קל מאוד להשתמש בו לא נכון ולכן רוב האנשים נתקלים בבאגים מוזרים כשמתחילים לכתוב אפקטים בקומפוננטות.</p>
<p>עוד כשכתבו לראשונה את המנגנון דן אברמוב דן עליו באריכות בבלוג שלו:
<a target="_blank" href="https://overreacted.io/a-complete-guide-to-useeffect/">https://overreacted.io/a-complete-guide-to-useeffect/</a></p>
<p>וזה עזר, אבל רק במידה מסוימת כי בסוף עד שלא מנסים את הדברים על קוד אמיתי קשה לראות את הבעיות.</p>
<p>בשנים שעברו מאז ש useEffect יצא נצבר לא מעט ידע על איך להשתמש בו נכון, למה לשים לב ומהם הבאגים הכי נפוצים שניתקל בהם. למרות שלימדתי את useEffect בקורס ריאקט השיעורים שם היו ממוקדים במה אפשר לבנות עם useEffect הרבה יותר מאשר במה אפשר לשבור איתו.</p>
<p>השבוע בעקבות כמה שאלות מתלמידים ישבתי לסדר את השיעורים האלה בקורס ולהקליט מחדש 3 סרטים על useEffect בגירסאות ארוכות ומפורטות (שעה של וידאו על useEffect אמרתם? יאללה קיבלתם). הפעם לקחתי כל דוגמה והראיתי את כל הטעויות שאפשר לעשות ואיך לזהות כל טעות.</p>
<p>אם יש לכם שעה פנויה ומנוי לאתר, ואתם כותבים ריאקט ביום יום, אני ממליץ להעיף מבט בסידרה המחודשת בקישור: <a href="https://www.tocode.co.il/bundles/react/toc">https://www.tocode.co.il/bundles/react/toc</a> שיעורים 25-27.</p>
<p>וכמו תמיד אם יש נושאים נוספים שהייתם רוצים לראות בקורסים כאן אל תתביישו לכתוב, אפשר דרך מסך צרו קשר כאן באתר.</p>
tag:www.tocode.co.il,2005:BlogPost/25652024-02-28T20:00:08+02:002024-02-28T20:00:08+02:00בואו נתקן את cycle בפייתוןינון פרקThu, 29 Feb 2024 06:00:00 +0200<p>הפונקציה cycle מתוך המודול itertools בפייתון לוקחת אוסף של דברים ומחזירה איטרטור אינסופי שכל פעם מחזיר את הדבר הבא מתוך הרצף במעגל. לדוגמה אפשר להשתמש בה עם המחרוזת abc באופן הבא:</p>
<pre><code class="language-python">print(list(itertools.islice(itertools.cycle("abc"), 10)))
</code></pre>
<p>ולקבל את התוצאה:</p>
<pre><code class="language-python">['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a']
</code></pre>
<p>אבל לסייקל יש בעיית ממשק - היא יכולה לקבל רק פרמטר אחד. לכן אם במקום רשימת האותיות הייתי רוצה להעביר רשימה של מספרים הייתי צריך לכתוב:</p>
<pre><code class="language-python">print(list(itertools.islice(itertools.cycle([1, 2, 3]), 10)))
</code></pre>
<p>וזה כבר מעייף לכתוב גם סוגריים עגולים וגם סוגריים מרובעים. מה עושים? מתקנים. אני לא שובר את הממשק של cycle אבל אני כן יכול לכתוב פונקציה עוטפת שתעשה לי חיים יותר קלים. הנה הקוד:</p>
<pre><code class="language-python">def easy_cycle(*args):
match args:
case [col] if hasattr(col, '__iter__'):
return itertools.cycle(col)
case _:
return itertools.cycle(args)
</code></pre>
<p>הפונקציה לוקחת רשימה של דברים ובודקת את הפרטים של הדבר הראשון שהיא קיבלה. אם זה משהו שאפשר לעשות עליו איטרציה נעביר אותו ל cycle בלי שינויים. בכל מצב אחר נעביר את רשימת הדברים שקיבלנו ל cycle.</p>
<p>התוצאה פשוטה ועכשיו הממשק מסתדר גם כשאני שוכח סוגריים מרובעים:</p>
<pre><code class="language-python">print(list(itertools.islice(easy_cycle("abc"), 10)))
print(list(itertools.islice(easy_cycle("a", "b", "c"), 10)))
print(list(itertools.islice(easy_cycle(["a", "b", "c"]), 10)))
</code></pre>
<p>נ.ב. היה נחמד אם מנגנון ה Type Hints של פייתון היה מאפשר לי להגדיר שערך ההחזר של הפונקציה שכתבתי זהה לערך ההחזר של הפונקציה <code dir="ltr">itertools.cycle</code>. בינתיים אפשר להעתיק חתימות או לוותר על ה Type Hint במקרה כזה.</p>
<p>הפונקציה cycle מתוך המודול itertools בפייתון לוקחת אוסף של דברים ומחזירה איטרטור אינסופי שכל פעם מחזיר את הדבר הבא מתוך הרצף במעגל. לדוגמה אפשר להשתמש בה עם המחרוזת abc באופן הבא:</p>
<pre><code class="language-python">print(list(itertools.islice(itertools.cycle("abc"), 10)))
</code></pre>
<p>ולקבל את התוצאה:</p>
<pre><code class="language-python">['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a']
</code></pre>
<p>אבל לסייקל יש בעיית ממשק - היא יכולה לקבל רק פרמטר אחד. לכן אם במקום רשימת האותיות הייתי רוצה להעביר רשימה של מספרים הייתי צריך לכתוב:</p>
<pre><code class="language-python">print(list(itertools.islice(itertools.cycle([1, 2, 3]), 10)))
</code></pre>
<p>וזה כבר מעייף לכתוב גם סוגריים עגולים וגם סוגריים מרובעים. מה עושים? מתקנים. אני לא שובר את הממשק של cycle אבל אני כן יכול לכתוב פונקציה עוטפת שתעשה לי חיים יותר קלים. הנה הקוד:</p>
<pre><code class="language-python">def easy_cycle(*args):
match args:
case [col] if hasattr(col, '__iter__'):
return itertools.cycle(col)
case _:
return itertools.cycle(args)
</code></pre>
<p>הפונקציה לוקחת רשימה של דברים ובודקת את הפרטים של הדבר הראשון שהיא קיבלה. אם זה משהו שאפשר לעשות עליו איטרציה נעביר אותו ל cycle בלי שינויים. בכל מצב אחר נעביר את רשימת הדברים שקיבלנו ל cycle.</p>
<p>התוצאה פשוטה ועכשיו הממשק מסתדר גם כשאני שוכח סוגריים מרובעים:</p>
<pre><code class="language-python">print(list(itertools.islice(easy_cycle("abc"), 10)))
print(list(itertools.islice(easy_cycle("a", "b", "c"), 10)))
print(list(itertools.islice(easy_cycle(["a", "b", "c"]), 10)))
</code></pre>
<p>נ.ב. היה נחמד אם מנגנון ה Type Hints של פייתון היה מאפשר לי להגדיר שערך ההחזר של הפונקציה שכתבתי זהה לערך ההחזר של הפונקציה <code dir="ltr">itertools.cycle</code>. בינתיים אפשר להעתיק חתימות או לוותר על ה Type Hint במקרה כזה.</p>
tag:www.tocode.co.il,2005:BlogPost/25642024-02-28T08:11:27+02:002024-02-28T08:11:41+02:00פיתרון Advent Of Code 2023 יום 13 בסקאלהינון פרקWed, 28 Feb 2024 06:00:00 +0200<p>כן אנחנו ממשיכים בסידרה הזאת ולא להאמין שעברנו כבר חצי מהאתגר והכל בשפת סקאלה. המטרה של הפוסטים האלה היא כמובן לדבר קצת על סקאלה אבל גם לתת לכם מוטיבציה וכיוון לפתור את זה בשפות אחרות שאתם לומדים. מוכנים? בואו נצלול לפרטים.</p>
<p>כן אנחנו ממשיכים בסידרה הזאת ולא להאמין שעברנו כבר חצי מהאתגר והכל בשפת סקאלה. המטרה של הפוסטים האלה היא כמובן לדבר קצת על סקאלה אבל גם לתת לכם מוטיבציה וכיוון לפתור את זה בשפות אחרות שאתם לומדים. מוכנים? בואו נצלול לפרטים.</p>
tag:www.tocode.co.il,2005:BlogPost/25632024-02-26T17:00:07+02:002024-02-26T17:00:07+02:00פוטנציאל (טיפ לחיפוש עבודה)ינון פרקTue, 27 Feb 2024 06:00:00 +0200<p>לפעמים אני אחפש עבודה שבדיוק מתאימה לכישורים שלי. במצב כזה אני צריך לשכנע את המעסיק שאני מכיר את החומר ומסוגל לבצע את המטלות בצורה הטובה ביותר.</p>
<p>לעתים יותר קרובות אני אחפש עבודה שתקדם אותי, עבודה שאני עדיין לא יודע איך לבצע אבל אני חושב שאוכל לגדול לשם. במצב כזה האתגר הוא לשכנע את המעסיק שיש לי את הפוטנציאל להצליח לעשות את המעבר (ממתכת למנהל, מ QA למתכנת, ממתכנת לפרודקט, או כל מעבר אחר שאתם חולמים עליו). כן זה הכי קל לשכנע בתוך הארגון אבל אפשר גם לשכנע אנשים זרים. כמה דברים שהייתי מנסה במצב כזה-</p>
<ol>
<li><p>להבליט בקורות חיים את הדברים הטובים שעשיתם בסטטוס הנוכחי או את הפרויקטים שביצעתם בהצלחה.</p></li>
<li><p>להתחיל את התהליך, אבל להיזהר עם הצגת תוצרים (מי שהיה 5 שנים מתכנת ורק התחיל לפני חודשיים קורס UI/UX אולי לא יצר עדיין את העיצובים הכי מטורפים).</p></li>
<li><p>להבליט שינויים מקצועיים קודמים שעשיתם, רצוי במכתב מקדים המצורף לקורות החיים.</p></li>
<li><p>להיות מוכנים לתקופת הכשרה או ירידה בשכר. לא לבוא בדרישות.</p></li>
<li><p>להתחיל בקטן - תוך כדי החיפוש שווה למצוא מקומות בתפקיד הנוכחי או אפילו בהתנדבות בהם אתם יכולים לעשות את הדבר שאתם רוצים לעשות. אותו מתכנת יוכל לבנות עיצובי UI/UX לאפליקציות דמיוניות או בהתנדבות. לא בטוח שהייתי מצרף אותם לקורות חיים אבל כן שיהיה משהו לדבר עליו בראיון.</p></li>
</ol>
<p>הדבר החשוב בשכנוע הוא להראות שאתם מבינים את האתגר ושכולם מדברים באותה שפה. בתור מעסיק הייתי מפחד לתת צ'אנס למי שהתחיל לפני חודשיים קורס עיצוב אבל כבר מתנהג כמו מעצב-על. אבל אם הבן אדם מבין שהוא עדיין רחוק מהיעד אבל מפוקס בדרך לשם, ומראה שהוא עשה שינויים דומים בעבר ומוכן לאתגר, ואני מאוד צריך מעצב וכבר מיואש מלמצוא אחד עם ניסיון, יש סיכוי שאקח את הסיכון ואתן את ההזדמנות.</p>
<p>לפעמים אני אחפש עבודה שבדיוק מתאימה לכישורים שלי. במצב כזה אני צריך לשכנע את המעסיק שאני מכיר את החומר ומסוגל לבצע את המטלות בצורה הטובה ביותר.</p>
<p>לעתים יותר קרובות אני אחפש עבודה שתקדם אותי, עבודה שאני עדיין לא יודע איך לבצע אבל אני חושב שאוכל לגדול לשם. במצב כזה האתגר הוא לשכנע את המעסיק שיש לי את הפוטנציאל להצליח לעשות את המעבר (ממתכת למנהל, מ QA למתכנת, ממתכנת לפרודקט, או כל מעבר אחר שאתם חולמים עליו). כן זה הכי קל לשכנע בתוך הארגון אבל אפשר גם לשכנע אנשים זרים. כמה דברים שהייתי מנסה במצב כזה-</p>
<ol>
<li><p>להבליט בקורות חיים את הדברים הטובים שעשיתם בסטטוס הנוכחי או את הפרויקטים שביצעתם בהצלחה.</p></li>
<li><p>להתחיל את התהליך, אבל להיזהר עם הצגת תוצרים (מי שהיה 5 שנים מתכנת ורק התחיל לפני חודשיים קורס UI/UX אולי לא יצר עדיין את העיצובים הכי מטורפים).</p></li>
<li><p>להבליט שינויים מקצועיים קודמים שעשיתם, רצוי במכתב מקדים המצורף לקורות החיים.</p></li>
<li><p>להיות מוכנים לתקופת הכשרה או ירידה בשכר. לא לבוא בדרישות.</p></li>
<li><p>להתחיל בקטן - תוך כדי החיפוש שווה למצוא מקומות בתפקיד הנוכחי או אפילו בהתנדבות בהם אתם יכולים לעשות את הדבר שאתם רוצים לעשות. אותו מתכנת יוכל לבנות עיצובי UI/UX לאפליקציות דמיוניות או בהתנדבות. לא בטוח שהייתי מצרף אותם לקורות חיים אבל כן שיהיה משהו לדבר עליו בראיון.</p></li>
</ol>
<p>הדבר החשוב בשכנוע הוא להראות שאתם מבינים את האתגר ושכולם מדברים באותה שפה. בתור מעסיק הייתי מפחד לתת צ'אנס למי שהתחיל לפני חודשיים קורס עיצוב אבל כבר מתנהג כמו מעצב-על. אבל אם הבן אדם מבין שהוא עדיין רחוק מהיעד אבל מפוקס בדרך לשם, ומראה שהוא עשה שינויים דומים בעבר ומוכן לאתגר, ואני מאוד צריך מעצב וכבר מיואש מלמצוא אחד עם ניסיון, יש סיכוי שאקח את הסיכון ואתן את ההזדמנות.</p>
tag:www.tocode.co.il,2005:BlogPost/25622024-02-25T17:00:08+02:002024-02-25T17:00:08+02:00בואו נשבור קצת ריאקטינון פרקMon, 26 Feb 2024 06:00:00 +0200<p>לריאקט יש כמה קונספטים פשוטים שרוב הזמן מצליחים להחזיק מערכת אבל לפעמים גם מביאים אותנו לקצוות של תסכול. זה תמיד קורה כי שברנו איזה כלל ריאקטי אין ספק, אבל בחיים קשה לעבוד רק לפי הכללים. בואו ניקח לדוגמה את הספריה <a target="_blank" href="https://www.npmjs.com/package/youtube-player">youtube-player</a> מ npm.</p>
<p>לריאקט יש כמה קונספטים פשוטים שרוב הזמן מצליחים להחזיק מערכת אבל לפעמים גם מביאים אותנו לקצוות של תסכול. זה תמיד קורה כי שברנו איזה כלל ריאקטי אין ספק, אבל בחיים קשה לעבוד רק לפי הכללים. בואו ניקח לדוגמה את הספריה <a target="_blank" href="https://www.npmjs.com/package/youtube-player">youtube-player</a> מ npm.</p>
tag:www.tocode.co.il,2005:BlogPost/25612024-02-24T21:00:08+02:002024-02-24T21:00:08+02:00משחקים עם סקאלה - סריקת פיסקאות בקובץינון פרקSun, 25 Feb 2024 06:00:00 +0200<p>האוביקט Source של סקאלה מאפשר לקרוא טקסט מהמון מקורות אבל הוא בדרך כלל נותן לנו את הטקסט בשורות, לדוגמה בשביל להדפיס קובץ שורה אחרי שורה אני יכול לכתוב-</p>
<pre><code class="language-scala">Source.fromFile("demo.txt").getLines().foreach(println)
</code></pre>
<p>בניסוי היום אני רוצה להריץ את אותה לולאה על פיסקאות, כלומר לקבל מעין רשימה של רשימות של שורות שמופיעות ברצף. לדוגמה בהינתן קובץ טקסט שנראה כך:</p>
<pre><code class="language-scala">Scala (/ˈskɑːlə/ SKAH-lah)[8] is a strong statically typed
high-level general-purpose programming language that supports
both object-oriented programming and functional programming.
Designed to be concise,[9] many of Scala's design decisions are
intended to address criticisms of Java.[7]
Scala source code can be compiled to Java bytecode and run on
a Java virtual machine (JVM).
Scala can also be compiled to JavaScript to run in a browser,
or directly to a native executable.
On the JVM Scala provides language interoperability with Java so that libraries written in either language may be referenced directly in Scala or Java code.[10] Like Java, Scala is object-oriented, and uses a syntax termed curly-brace which is similar to the language C. Since Scala 3, there is also an option to use the off-side rule (indenting) to structure blocks, and its use is advised. Martin Odersky has said that this turned out to be the most productive change introduced in Scala 3.[11]
</code></pre>
<p>אני יודע שאני יכול להפעיל את getLines כדי לקבל רשימה של כל השורות, אבל אני רוצה לבנות פונקציה שתהפוך את זה לרשימה של רשימות של שורות, כאשר בכל תת רשימה יהיו השורות הצמודות. סך הכל יהיו לי 4 רשימות שיתאימו לארבעת הפיסקאות בקובץ.</p>
<p>אחרי שתהיה לנו פונקציה כזאת (נקרא לה בשביל המשחק <code dir="ltr">toParagraphs</code>, אפשר יהיה לכתוב קוד כזה כדי למצוא את הפיסקה עם הכי הרבה שורות:</p>
<pre><code class="language-scala"> Source
.fromResource("demo.txt")
.getLines()
.toParagraphs
.maxBy(_.size)
.foreach(println)
</code></pre>
<p>או כזה כדי למצוא את הפיסקה הארוכה ביותר (עם הכי הרבה תווים):</p>
<pre><code class="language-scala"> Source
.fromResource("demo.txt")
.getLines()
.toParagraphs
.maxBy(_.mkString.length)
.foreach(println)
</code></pre>
<p>המימוש של toParagraphs היה די פשוט אחרי שהבנתי את המנגנון של איטרטורים בסקאלה. בגדול איטרטור הוא משהו שמאפשר לרוץ על אוסף של פריטים, ולכן בשביל לשנות את איך שרצים על הפריטים צריך לבנות איטרטור חדש. האיטרטור החדש מקבל כקלט איטרטור שרץ על השורות (התוצאה של getLines) והקסם שלו קורה בפונקציה next שצריכה להחזיר את הפריט הבא. במקום להחזיר שורה הפונקציה באיטרטור החדש שלי מחזירה רשימה של שורות. קוד האיטרטור החדש הוא לכן:</p>
<pre><code class="language-scala">class ChunkedIterator[T](iterator: Iterator[T])(p: (T => Boolean)) extends Iterator[List[T]] {
override def hasNext: Boolean = iterator.hasNext
override def next(): List[T] = {
if (!hasNext) throw new NoSuchElementException("next on empty iterator")
iterator.takeWhile(p).toList
}
}
</code></pre>
<p>וכן בשביל המשחק בניתי את האיטרטור החדש בצורה גנרית כך שבעתיד נוכל להשתמש בו כדי לפצל דברים לקבוצות לפי פרדיקטים אחרים. נשים לב גם לשימוש ב <code dir="ltr">takeWhile</code>. זאת פונקציה מעניינת כי היא מחזירה את רשימת הפריטים שמתאימים לפרדיקט אבל מדלגת על הפריט שלא מתאים, וכך האיטרטור לא מחזיר את השורות הריקות.</p>
<p>אחרי שבנינו את האיטרטור אפשר להמשיך לפונקציה <code dir="ltr">toParagraphs</code> שבסך הכל צריכה ליצור איטרטור כזה ולהעביר את הפרדיקט שמזהה שורות ריקות. בשביל שיהיה קל לעבוד איתה הוספתי אותה למחלקה Iterator זה הקוד:</p>
<pre><code class="language-scala">extension (i: Iterator[String]) {
def toParagraphs: ChunkedIterator[String] = {
ChunkedIterator[String](i) { f => f.nonEmpty }
}
}
</code></pre>
<p>אז נכון הייתי שמח אם המנגנון הזה היה מובנה בסקאלה בדומה ל <a target="_blank" href="https://clojuredocs.org/clojure.core/partition-by">partition-by</a> של קלוז'ר או <a target="_blank" href="https://hexdocs.pm/elixir/1.12/Enum.html#chunk_by/2">chunk_by</a> של אליקסיר, אבל לפעמים צריך לקבל גם את הקשיים של החיים ולעבוד עם מה שיש.</p>
<p>האוביקט Source של סקאלה מאפשר לקרוא טקסט מהמון מקורות אבל הוא בדרך כלל נותן לנו את הטקסט בשורות, לדוגמה בשביל להדפיס קובץ שורה אחרי שורה אני יכול לכתוב-</p>
<pre><code class="language-scala">Source.fromFile("demo.txt").getLines().foreach(println)
</code></pre>
<p>בניסוי היום אני רוצה להריץ את אותה לולאה על פיסקאות, כלומר לקבל מעין רשימה של רשימות של שורות שמופיעות ברצף. לדוגמה בהינתן קובץ טקסט שנראה כך:</p>
<pre><code class="language-scala">Scala (/ˈskɑːlə/ SKAH-lah)[8] is a strong statically typed
high-level general-purpose programming language that supports
both object-oriented programming and functional programming.
Designed to be concise,[9] many of Scala's design decisions are
intended to address criticisms of Java.[7]
Scala source code can be compiled to Java bytecode and run on
a Java virtual machine (JVM).
Scala can also be compiled to JavaScript to run in a browser,
or directly to a native executable.
On the JVM Scala provides language interoperability with Java so that libraries written in either language may be referenced directly in Scala or Java code.[10] Like Java, Scala is object-oriented, and uses a syntax termed curly-brace which is similar to the language C. Since Scala 3, there is also an option to use the off-side rule (indenting) to structure blocks, and its use is advised. Martin Odersky has said that this turned out to be the most productive change introduced in Scala 3.[11]
</code></pre>
<p>אני יודע שאני יכול להפעיל את getLines כדי לקבל רשימה של כל השורות, אבל אני רוצה לבנות פונקציה שתהפוך את זה לרשימה של רשימות של שורות, כאשר בכל תת רשימה יהיו השורות הצמודות. סך הכל יהיו לי 4 רשימות שיתאימו לארבעת הפיסקאות בקובץ.</p>
<p>אחרי שתהיה לנו פונקציה כזאת (נקרא לה בשביל המשחק <code dir="ltr">toParagraphs</code>, אפשר יהיה לכתוב קוד כזה כדי למצוא את הפיסקה עם הכי הרבה שורות:</p>
<pre><code class="language-scala"> Source
.fromResource("demo.txt")
.getLines()
.toParagraphs
.maxBy(_.size)
.foreach(println)
</code></pre>
<p>או כזה כדי למצוא את הפיסקה הארוכה ביותר (עם הכי הרבה תווים):</p>
<pre><code class="language-scala"> Source
.fromResource("demo.txt")
.getLines()
.toParagraphs
.maxBy(_.mkString.length)
.foreach(println)
</code></pre>
<p>המימוש של toParagraphs היה די פשוט אחרי שהבנתי את המנגנון של איטרטורים בסקאלה. בגדול איטרטור הוא משהו שמאפשר לרוץ על אוסף של פריטים, ולכן בשביל לשנות את איך שרצים על הפריטים צריך לבנות איטרטור חדש. האיטרטור החדש מקבל כקלט איטרטור שרץ על השורות (התוצאה של getLines) והקסם שלו קורה בפונקציה next שצריכה להחזיר את הפריט הבא. במקום להחזיר שורה הפונקציה באיטרטור החדש שלי מחזירה רשימה של שורות. קוד האיטרטור החדש הוא לכן:</p>
<pre><code class="language-scala">class ChunkedIterator[T](iterator: Iterator[T])(p: (T => Boolean)) extends Iterator[List[T]] {
override def hasNext: Boolean = iterator.hasNext
override def next(): List[T] = {
if (!hasNext) throw new NoSuchElementException("next on empty iterator")
iterator.takeWhile(p).toList
}
}
</code></pre>
<p>וכן בשביל המשחק בניתי את האיטרטור החדש בצורה גנרית כך שבעתיד נוכל להשתמש בו כדי לפצל דברים לקבוצות לפי פרדיקטים אחרים. נשים לב גם לשימוש ב <code dir="ltr">takeWhile</code>. זאת פונקציה מעניינת כי היא מחזירה את רשימת הפריטים שמתאימים לפרדיקט אבל מדלגת על הפריט שלא מתאים, וכך האיטרטור לא מחזיר את השורות הריקות.</p>
<p>אחרי שבנינו את האיטרטור אפשר להמשיך לפונקציה <code dir="ltr">toParagraphs</code> שבסך הכל צריכה ליצור איטרטור כזה ולהעביר את הפרדיקט שמזהה שורות ריקות. בשביל שיהיה קל לעבוד איתה הוספתי אותה למחלקה Iterator זה הקוד:</p>
<pre><code class="language-scala">extension (i: Iterator[String]) {
def toParagraphs: ChunkedIterator[String] = {
ChunkedIterator[String](i) { f => f.nonEmpty }
}
}
</code></pre>
<p>אז נכון הייתי שמח אם המנגנון הזה היה מובנה בסקאלה בדומה ל <a target="_blank" href="https://clojuredocs.org/clojure.core/partition-by">partition-by</a> של קלוז'ר או <a target="_blank" href="https://hexdocs.pm/elixir/1.12/Enum.html#chunk_by/2">chunk_by</a> של אליקסיר, אבל לפעמים צריך לקבל גם את הקשיים של החיים ולעבוד עם מה שיש.</p>
tag:www.tocode.co.il,2005:BlogPost/25602024-02-23T21:00:08+02:002024-02-23T21:00:08+02:00איך לזהות תירוצים לאי התקדמות מקצועיתינון פרקSat, 24 Feb 2024 06:00:00 +0200<p>יש המון סיבות טובות להתרחק מטכנולוגיה חדשה כמו למשל-</p>
<ol>
<li>אני כבר ממש טוב ב X, בשביל מה לבזבז את הזמן על Y.</li>
<li>אין לי צורך בזה לעבודה שלי.</li>
<li>זה הכל הייפ, תכף ההתלהבות תיגמר וכולם יחזרו לטכנולוגיה שהיתה קודם.</li>
<li>אין מספיק אנשים בתעשייה שמשתמשים ב Y וחבל לי לבזבז את הזמן על משהו שלא יקדם אותי.</li>
<li>הייתי שמח ללמוד את Y אבל כרגע אנחנו בדיוק באמצע פרויקט, אחרי הגירסה אתחיל להסתכל על זה.</li>
</ol>
<p>והמון פעמים הסיבות האלה ממש נכונות. יש תקופות בחיים שלא צריך ללמוד שום דבר חדש והדבר החשוב הוא להתקדם בפרויקט המדהים שאנחנו בונים. אם כל היום היינו לומדים טכנולוגיות חדשות לא היה זמן לעבוד.</p>
<p>הבעיה היא שלפעמים מה שנשמע כמו סיבה ממש טובה הוא בעצם תירוץ שמרגיש כמו סיבה. תירוץ שמתחזה כל כך טוב שאפילו הבן אדם שנותן אותו לא מצליח לשים לב שיש פה משהו חשוד.</p>
<p>טריק אחד שעובד בשבילי כדי לזהות מתי אני באמת עסוק מדי ומתי אני רק מתחמק הוא להאריך את חלון הזמן עליו אני מסתכל. במקום להסתכל על הדבר שאני צריך ללמוד עכשיו אני אסתכל אחורה ואשאל-</p>
<ol>
<li>כמה טכנולוגיות חדשות למדתי בשנה האחרונה?</li>
<li>כמה כלים חדשים שילבתי בעבודה שלי בשנה האחרונה בצורה שתרמה לפרודוקטיביות?</li>
<li>האם יש לי רשימת טכנולוגיות ללימוד מעולם התוכן שלי שאני יודע שאני צריך לחקור? ואם כן האם הצלחתי לקצר אותה בשנה האחרונה?</li>
</ol>
<p>כשמסתכלים על שנה אחורה במקום על מחר בבוקר יותר קל להבין את הסיטואציה. כמה מוצדקות שלא יהיו הסיבות שיש לי היום הן בטח לא היו שם לפני חודש, חודשיים או חצי שנה. יותר מזה, רק לשים לב שבשנה האחרונה לא לקחת אף צעד קדימה זה חוויה מטלטלת שיכולה לשנות סדרי עדיפויות. יש למוח שלנו יכולת מופלאה להעביר שנים על טייס אוטומטי. זיהוי המנגנון הזה בזמן הוא ההזדמנות שלנו לחיים שמחים יותר.</p>
<p>יש המון סיבות טובות להתרחק מטכנולוגיה חדשה כמו למשל-</p>
<ol>
<li>אני כבר ממש טוב ב X, בשביל מה לבזבז את הזמן על Y.</li>
<li>אין לי צורך בזה לעבודה שלי.</li>
<li>זה הכל הייפ, תכף ההתלהבות תיגמר וכולם יחזרו לטכנולוגיה שהיתה קודם.</li>
<li>אין מספיק אנשים בתעשייה שמשתמשים ב Y וחבל לי לבזבז את הזמן על משהו שלא יקדם אותי.</li>
<li>הייתי שמח ללמוד את Y אבל כרגע אנחנו בדיוק באמצע פרויקט, אחרי הגירסה אתחיל להסתכל על זה.</li>
</ol>
<p>והמון פעמים הסיבות האלה ממש נכונות. יש תקופות בחיים שלא צריך ללמוד שום דבר חדש והדבר החשוב הוא להתקדם בפרויקט המדהים שאנחנו בונים. אם כל היום היינו לומדים טכנולוגיות חדשות לא היה זמן לעבוד.</p>
<p>הבעיה היא שלפעמים מה שנשמע כמו סיבה ממש טובה הוא בעצם תירוץ שמרגיש כמו סיבה. תירוץ שמתחזה כל כך טוב שאפילו הבן אדם שנותן אותו לא מצליח לשים לב שיש פה משהו חשוד.</p>
<p>טריק אחד שעובד בשבילי כדי לזהות מתי אני באמת עסוק מדי ומתי אני רק מתחמק הוא להאריך את חלון הזמן עליו אני מסתכל. במקום להסתכל על הדבר שאני צריך ללמוד עכשיו אני אסתכל אחורה ואשאל-</p>
<ol>
<li>כמה טכנולוגיות חדשות למדתי בשנה האחרונה?</li>
<li>כמה כלים חדשים שילבתי בעבודה שלי בשנה האחרונה בצורה שתרמה לפרודוקטיביות?</li>
<li>האם יש לי רשימת טכנולוגיות ללימוד מעולם התוכן שלי שאני יודע שאני צריך לחקור? ואם כן האם הצלחתי לקצר אותה בשנה האחרונה?</li>
</ol>
<p>כשמסתכלים על שנה אחורה במקום על מחר בבוקר יותר קל להבין את הסיטואציה. כמה מוצדקות שלא יהיו הסיבות שיש לי היום הן בטח לא היו שם לפני חודש, חודשיים או חצי שנה. יותר מזה, רק לשים לב שבשנה האחרונה לא לקחת אף צעד קדימה זה חוויה מטלטלת שיכולה לשנות סדרי עדיפויות. יש למוח שלנו יכולת מופלאה להעביר שנים על טייס אוטומטי. זיהוי המנגנון הזה בזמן הוא ההזדמנות שלנו לחיים שמחים יותר.</p>
tag:www.tocode.co.il,2005:BlogPost/25592024-02-22T21:00:09+02:002024-02-22T21:00:09+02:00משחקים עם מקביליות בסקאלה (חלק 2)ינון פרקFri, 23 Feb 2024 06:00:00 +0200<p>בחלק <a href="https://www.tocode.co.il/blog/2024-02-scala-pmap">הקודם</a> של הפוסט כתבתי על pmap ואיך אפשר להשתמש בו כדי לחלק פעולה חישובית למספר תהליכונים כדי לשפר ביצועים. היום אני רוצה לדבר על עבודת IO, על המגבלה של Thread Pool במיקבול משימות הקשורות ל IO ועל הפיתרון עם Virtual Threads. </p>
<p>בחלק <a href="https://www.tocode.co.il/blog/2024-02-scala-pmap">הקודם</a> של הפוסט כתבתי על pmap ואיך אפשר להשתמש בו כדי לחלק פעולה חישובית למספר תהליכונים כדי לשפר ביצועים. היום אני רוצה לדבר על עבודת IO, על המגבלה של Thread Pool במיקבול משימות הקשורות ל IO ועל הפיתרון עם Virtual Threads. </p>
tag:www.tocode.co.il,2005:BlogPost/25582024-02-21T16:00:07+02:002024-02-21T16:00:07+02:00ביי ביי map בשביל לשנות ערכים במיקום מסוים ב JavaScriptינון פרקThu, 22 Feb 2024 06:00:00 +0200<p>אם אי פעם שמרתם מערך בסטייט של קומפוננטת ריאקט אתם וודאי זוכרים את הרגע שניסיתם לשנות רק ערך אחד במערך ולהעביר את התוצאה ל set, רק בשביל לגלות ש setState לא עושה כלום כי המערך עצמו לא השתנה. אחרי זה למדתם להשתמש ב map כדי ליצור מערך חדש עם שינוי אלמנט באינדקס מסוים-</p>
<pre><code class="language-javascript">const newItems = oldItems.map((item, index) =>
index === 1 ? 99 : item);
</code></pre>
<p>ואז הגיעה immer ושוב הצלחנו לכתוב קוד רגיל אבל ידענו בלב שזה לא בדיוק זה ושמנגנון הפרוקסים של immer עובד אומנם ברוב המקרים אבל בסוף תמיד מחכה איזה באג (לא בגלל immer ברור, אתם פשוט מחזיקים את זה לא נכון).</p>
<p>בקיצור קיטורים בצד לאחרונה JavaScript קיבלה בכל הדפדפנים תמיכה בפיתרון מובנה לשינוי מערכים לפי אינדקס ובלי map. זה נראה ככה-</p>
<pre><code class="language-javascript">const newItems = oldItems.with(1, 99);
</code></pre>
<p>נשים לב שאי אפשר לכתוב אחרי סוף המערך כך שזה נכשל:</p>
<pre><code class="language-javascript">[].with(99, 0)
</code></pre>
<p>בנוסף with שובר מערכים מרווחים, אבל אני לא בטוח כמה נזק זה עלול לגרום. כלומר הקוד הזה:</p>
<pre><code class="language-javascript">Array(10).forEach(() => console.log('1'))
</code></pre>
<p>והקוד הזה:</p>
<pre><code class="language-javascript">Array(10).with(0, 0).forEach(() => console.log('1'))
</code></pre>
<p>יעשו דברים שונים - הראשון לא ידפיס כלום, השני ידפיס 10 פעמים את ההודעה. אין תמיכה במספר אינדקסים לכתיבה לתוך מערכים מקוננים, אבל כן יש תמיכה באינדקסים שליליים לכתיבה מסוף המערך:</p>
<pre><code class="language-javascript">[1, 2, 3, 4, 5].with(-1, 10)
</code></pre>
<p>אם אי פעם שמרתם מערך בסטייט של קומפוננטת ריאקט אתם וודאי זוכרים את הרגע שניסיתם לשנות רק ערך אחד במערך ולהעביר את התוצאה ל set, רק בשביל לגלות ש setState לא עושה כלום כי המערך עצמו לא השתנה. אחרי זה למדתם להשתמש ב map כדי ליצור מערך חדש עם שינוי אלמנט באינדקס מסוים-</p>
<pre><code class="language-javascript">const newItems = oldItems.map((item, index) =>
index === 1 ? 99 : item);
</code></pre>
<p>ואז הגיעה immer ושוב הצלחנו לכתוב קוד רגיל אבל ידענו בלב שזה לא בדיוק זה ושמנגנון הפרוקסים של immer עובד אומנם ברוב המקרים אבל בסוף תמיד מחכה איזה באג (לא בגלל immer ברור, אתם פשוט מחזיקים את זה לא נכון).</p>
<p>בקיצור קיטורים בצד לאחרונה JavaScript קיבלה בכל הדפדפנים תמיכה בפיתרון מובנה לשינוי מערכים לפי אינדקס ובלי map. זה נראה ככה-</p>
<pre><code class="language-javascript">const newItems = oldItems.with(1, 99);
</code></pre>
<p>נשים לב שאי אפשר לכתוב אחרי סוף המערך כך שזה נכשל:</p>
<pre><code class="language-javascript">[].with(99, 0)
</code></pre>
<p>בנוסף with שובר מערכים מרווחים, אבל אני לא בטוח כמה נזק זה עלול לגרום. כלומר הקוד הזה:</p>
<pre><code class="language-javascript">Array(10).forEach(() => console.log('1'))
</code></pre>
<p>והקוד הזה:</p>
<pre><code class="language-javascript">Array(10).with(0, 0).forEach(() => console.log('1'))
</code></pre>
<p>יעשו דברים שונים - הראשון לא ידפיס כלום, השני ידפיס 10 פעמים את ההודעה. אין תמיכה במספר אינדקסים לכתיבה לתוך מערכים מקוננים, אבל כן יש תמיכה באינדקסים שליליים לכתיבה מסוף המערך:</p>
<pre><code class="language-javascript">[1, 2, 3, 4, 5].with(-1, 10)
</code></pre>
tag:www.tocode.co.il,2005:BlogPost/25572024-02-20T17:00:08+02:002024-02-20T17:00:08+02:00היום למדתי - איפוס הגדרות postcss בפרויקט viteינון פרקWed, 21 Feb 2024 06:00:00 +0200<p>לפעמים קשה להבין למה פרויקטי Front End לא מצליחים לעבוד טוב יחד, או שאולי הבעיה היא אצלנו המתכנתים שפשוט דוחפים עוד ועוד שטויות לפרויקט ובסוף מתפלאים כשמופיעות הודעות שגיאה מוזרות.</p>
<p>הסיפור שהיום לקח לי יותר מדי שעות מהחיים קשור ל postcss ולהתנהגות המוזרה שלו בתוך פרויקט vite - בהעדר הגדרה אחרת, postcss יחפש קובץ הגדרות במעלה עץ התיקיות עד לתיקיית הבית. בשביל הניסוי שמתי בתיקיית הבית קובץ בשם postcss.config.js עם התוכן הבא:</p>
<pre><code class="language-sh">module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
</code></pre>
<p>ואז יצרתי פרויקט vite חדש לגמרי בתיקייה:</p>
<pre><code class="language-text">/Users/ynonp/a/b/c/d/helloworld
</code></pre>
<p>הפעלתי npm run build בתיקיה וקיבלתי את הודעת השגיאה הבאה:</p>
<pre><code class="language-text">> helloworld@0.0.0 build
> tsc && vite build
vite v5.1.3 building for production...
transforming (1) index.htmlnode:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
[Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/ynonp/a/b/c/d/helloworld): [Error] Loading PostCSS Plugin failed: Cannot find module 'tailwindcss'
Require stack:
- /Users/ynonp/postcss.config.js
(@/Users/ynonp/postcss.config.js)
Error: Loading PostCSS Plugin failed: Cannot find module 'tailwindcss'
Require stack:
- /Users/ynonp/postcss.config.js
(@/Users/ynonp/postcss.config.js)
at load (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28883:11)
at file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28908:16
at Array.map (<anonymous>)
at plugins (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28907:8)
at processResult (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28977:14)
at file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:29107:14]
Node.js v21.5.0
</code></pre>
<p>וכן זה לקח הרבה יותר זמן לגלות שהסיבה להודעה היא בעצם הקובץ postcss.config.js שנמצא בתיקיית הבית שלי.</p>
<p>פיתרון? די פשוט מסתבר אחרי שמבינים את הבעיה. יוצרים קובץ vite.config.js בתיקיית הפרויקט עם התוכן הבא והכל מסתדר:</p>
<pre><code class="language-javascript">import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
css: {
postcss: {},
},
})
</code></pre>
<p>אז נכון אני לא אקבל את החצי יום שלי בחזרה, אבל אולי הפוסט הזה יחסוך לכם כמה שעות.</p>
<p>לפעמים קשה להבין למה פרויקטי Front End לא מצליחים לעבוד טוב יחד, או שאולי הבעיה היא אצלנו המתכנתים שפשוט דוחפים עוד ועוד שטויות לפרויקט ובסוף מתפלאים כשמופיעות הודעות שגיאה מוזרות.</p>
<p>הסיפור שהיום לקח לי יותר מדי שעות מהחיים קשור ל postcss ולהתנהגות המוזרה שלו בתוך פרויקט vite - בהעדר הגדרה אחרת, postcss יחפש קובץ הגדרות במעלה עץ התיקיות עד לתיקיית הבית. בשביל הניסוי שמתי בתיקיית הבית קובץ בשם postcss.config.js עם התוכן הבא:</p>
<pre><code class="language-sh">module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
</code></pre>
<p>ואז יצרתי פרויקט vite חדש לגמרי בתיקייה:</p>
<pre><code class="language-text">/Users/ynonp/a/b/c/d/helloworld
</code></pre>
<p>הפעלתי npm run build בתיקיה וקיבלתי את הודעת השגיאה הבאה:</p>
<pre><code class="language-text">> helloworld@0.0.0 build
> tsc && vite build
vite v5.1.3 building for production...
transforming (1) index.htmlnode:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
[Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/ynonp/a/b/c/d/helloworld): [Error] Loading PostCSS Plugin failed: Cannot find module 'tailwindcss'
Require stack:
- /Users/ynonp/postcss.config.js
(@/Users/ynonp/postcss.config.js)
Error: Loading PostCSS Plugin failed: Cannot find module 'tailwindcss'
Require stack:
- /Users/ynonp/postcss.config.js
(@/Users/ynonp/postcss.config.js)
at load (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28883:11)
at file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28908:16
at Array.map (<anonymous>)
at plugins (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28907:8)
at processResult (file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:28977:14)
at file:///Users/ynonp/a/b/c/d/helloworld/node_modules/vite/dist/node/chunks/dep-stQc5rCc.js:29107:14]
Node.js v21.5.0
</code></pre>
<p>וכן זה לקח הרבה יותר זמן לגלות שהסיבה להודעה היא בעצם הקובץ postcss.config.js שנמצא בתיקיית הבית שלי.</p>
<p>פיתרון? די פשוט מסתבר אחרי שמבינים את הבעיה. יוצרים קובץ vite.config.js בתיקיית הפרויקט עם התוכן הבא והכל מסתדר:</p>
<pre><code class="language-javascript">import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
css: {
postcss: {},
},
})
</code></pre>
<p>אז נכון אני לא אקבל את החצי יום שלי בחזרה, אבל אולי הפוסט הזה יחסוך לכם כמה שעות.</p>
tag:www.tocode.co.il,2005:BlogPost/25562024-02-19T20:00:07+02:002024-02-19T20:00:07+02:00עוד מחשבה על תבנית ה index.ts שמייצא הכלינון פרקTue, 20 Feb 2024 06:00:00 +0200<p>בואו נדמיין פרויקט ריאקט שיש בו תיקייה בשם <code dir="ltr">src/components/HomePage</code> ובתוכה ערימה של תיקיות וקובץ אחד בשם <code dir="ltr">index.ts</code>:</p>
<pre><code class="language-text">.
├── EmptyState
│ ├── EmptyState.tsx
│ └── index.ts
├── ErrorState
│ ├── ErrorState.tsx
│ ├── ErrorState.types.ts
│ └── index.ts
├── RecentArticles
│ ├── RecentArticles.tsx
│ ├── RecentArticles.types.ts
│ └── index.ts
├── RecentArticlesCard
│ ├── RecentArticleCard.tsx
│ ├── RecentArticleCard.types.ts
│ └── index.ts
├── RecentArticlesContent
│ ├── RecentArticlesContent.tsx
│ ├── RecentArticlesContent.types.ts
│ └── index.ts
└── index.ts
</code></pre>
<p>תוכן הקובץ index.ts יהיה:</p>
<pre><code class="language-typescript">export * from './EmptyState';
export * from './ErrorState';
export * from './RecentArticles';
export * from './RecentArticlesCard';
export * from './RecentArticlesContent';
</code></pre>
<p>למה שמישהו יכתוב ככה? מה היתרונות ומה החסרונות? והאם כדאי לנו גם להשתמש בתבנית זו בפרויקטים שלנו? (כן אני יודע רובכם כבר משתמשים. בגלל זה הפוסט)</p>
<p>בואו נדמיין פרויקט ריאקט שיש בו תיקייה בשם <code dir="ltr">src/components/HomePage</code> ובתוכה ערימה של תיקיות וקובץ אחד בשם <code dir="ltr">index.ts</code>:</p>
<pre><code class="language-text">.
├── EmptyState
│ ├── EmptyState.tsx
│ └── index.ts
├── ErrorState
│ ├── ErrorState.tsx
│ ├── ErrorState.types.ts
│ └── index.ts
├── RecentArticles
│ ├── RecentArticles.tsx
│ ├── RecentArticles.types.ts
│ └── index.ts
├── RecentArticlesCard
│ ├── RecentArticleCard.tsx
│ ├── RecentArticleCard.types.ts
│ └── index.ts
├── RecentArticlesContent
│ ├── RecentArticlesContent.tsx
│ ├── RecentArticlesContent.types.ts
│ └── index.ts
└── index.ts
</code></pre>
<p>תוכן הקובץ index.ts יהיה:</p>
<pre><code class="language-typescript">export * from './EmptyState';
export * from './ErrorState';
export * from './RecentArticles';
export * from './RecentArticlesCard';
export * from './RecentArticlesContent';
</code></pre>
<p>למה שמישהו יכתוב ככה? מה היתרונות ומה החסרונות? והאם כדאי לנו גם להשתמש בתבנית זו בפרויקטים שלנו? (כן אני יודע רובכם כבר משתמשים. בגלל זה הפוסט)</p>
tag:www.tocode.co.il,2005:BlogPost/25552024-02-18T17:00:07+02:002024-02-18T17:00:07+02:00איך ליצור דף Github Pages לפרויקט שלךינון פרקMon, 19 Feb 2024 06:00:00 +0200<p>בין אם אתם בונים פרויקט בשביל העולם או רק בשביל קורות החיים, דף ווב לפרויקט הזה שמסביר מה הוא עושה יכול לעזור לאנשים למצוא אתכם, או לבוסים פוטנציאליים להתרשם מהעבודה שלכם. ואם כבר הפרויקט שמור על גיטהאב בואו נבנה לו גם דף מידע שיאוחסן באותו מקום, בתשתית של Github Pages.</p>
<p>בין אם אתם בונים פרויקט בשביל העולם או רק בשביל קורות החיים, דף ווב לפרויקט הזה שמסביר מה הוא עושה יכול לעזור לאנשים למצוא אתכם, או לבוסים פוטנציאליים להתרשם מהעבודה שלכם. ואם כבר הפרויקט שמור על גיטהאב בואו נבנה לו גם דף מידע שיאוחסן באותו מקום, בתשתית של Github Pages.</p>
tag:www.tocode.co.il,2005:BlogPost/25542024-02-17T16:00:07+02:002024-02-17T16:00:07+02:00זאת כבר לא הבורות שלנו שמעכבת אותנוינון פרקSun, 18 Feb 2024 06:00:00 +0200<p>נתן סובו מ zed זרק את המשפט הבא <a target="_blank" href="https://zed.dev/blog/we-have-to-start-over">בראיון</a> על הטכנולוגיה של זד, או יותר נכון על הבחירה שלהם לזרוק את Atom שהיה עורך הטקסט שהם עבדו עליו לפני ולהתחיל הכל מחדש. הוא אמר-</p>
<p>"הגענו לנקודה שזו כבר לא היתה הבורות שלנו שמעכבת אותנו, זאת באמת הפלטפורמה"</p>
<p>זה היה אחרי שלוש שנים של עבודה על Atom. שלוש שנים של ניסיונות לשפר את הביצועים. שלוש שנים של התמודדות עם האתגר של כתיבת Desktop Application מהיר בכלים ווביים.</p>
<p>האם היה עדיף לו הם היו מתחילים עם Rust כבר ב 2014 במקום לכתוב את Atom? ברור שלא. קודם כל כי ראסט לא היתה קיימת ב 2014, אבל יותר חשוב הם עוד לא הכירו מספיק טוב את הבעיה, עוד לא הבינו לעומק את האלגוריתמים שצריך לפתח בשביל לבנות עורך טקסט ואת הדרישות והצרכים של המתכנתים שאמורים להשתמש באותו עורך. העבודה על אטום, יחד עם ההתקדמות הטכנולוגית שקרתה באותו הזמן, הביאו אותם לנקודה שבה היה עדיף להתחיל מחדש.</p>
<p>וזה לקח טוב לכל פרויקט שאנחנו בונים - בתחילת הדרך, כשאנחנו עדיין לא מכירים מספיק את הבעיה ואת העולם הטכנולוגי שלה, זה בסדר לקחת בחירות קלות כדי להתקדם. יותר מבסדר, זאת הדרך היחידה קדימה. אחרי שנתגבר על הבורות שלנו ונבין למה אנחנו צריכים את הכלים המתקדמים יותר נהיה במקום הרבה יותר בשל כדי לשפר את הטכנולוגיה, או לזרוק הכל ולהתחיל מחדש משהו טוב יותר.</p>
<p>נתן סובו מ zed זרק את המשפט הבא <a target="_blank" href="https://zed.dev/blog/we-have-to-start-over">בראיון</a> על הטכנולוגיה של זד, או יותר נכון על הבחירה שלהם לזרוק את Atom שהיה עורך הטקסט שהם עבדו עליו לפני ולהתחיל הכל מחדש. הוא אמר-</p>
<p>"הגענו לנקודה שזו כבר לא היתה הבורות שלנו שמעכבת אותנו, זאת באמת הפלטפורמה"</p>
<p>זה היה אחרי שלוש שנים של עבודה על Atom. שלוש שנים של ניסיונות לשפר את הביצועים. שלוש שנים של התמודדות עם האתגר של כתיבת Desktop Application מהיר בכלים ווביים.</p>
<p>האם היה עדיף לו הם היו מתחילים עם Rust כבר ב 2014 במקום לכתוב את Atom? ברור שלא. קודם כל כי ראסט לא היתה קיימת ב 2014, אבל יותר חשוב הם עוד לא הכירו מספיק טוב את הבעיה, עוד לא הבינו לעומק את האלגוריתמים שצריך לפתח בשביל לבנות עורך טקסט ואת הדרישות והצרכים של המתכנתים שאמורים להשתמש באותו עורך. העבודה על אטום, יחד עם ההתקדמות הטכנולוגית שקרתה באותו הזמן, הביאו אותם לנקודה שבה היה עדיף להתחיל מחדש.</p>
<p>וזה לקח טוב לכל פרויקט שאנחנו בונים - בתחילת הדרך, כשאנחנו עדיין לא מכירים מספיק את הבעיה ואת העולם הטכנולוגי שלה, זה בסדר לקחת בחירות קלות כדי להתקדם. יותר מבסדר, זאת הדרך היחידה קדימה. אחרי שנתגבר על הבורות שלנו ונבין למה אנחנו צריכים את הכלים המתקדמים יותר נהיה במקום הרבה יותר בשל כדי לשפר את הטכנולוגיה, או לזרוק הכל ולהתחיל מחדש משהו טוב יותר.</p>
tag:www.tocode.co.il,2005:BlogPost/25532024-02-16T17:00:08+02:002024-02-16T17:00:08+02:00פיתרון Advent Of Code 2023 יום 12 ב Scalaינון פרקSat, 17 Feb 2024 06:00:00 +0200<p>אני מקווה שלא התעייפתם מהחידות של אריק, אני בטוח לא התעייפתי וממשיך (גם אם לאט) במטרה לסיים יום אחד את כל 25 החידות. הפירסום היום מלווה באופטימיות זהירה שכן אנחנו כמעט בחצי הדרך. בואו נראה מה מחכה לנו ביום 12, איזה פיתרון לא עבד ומה בסוף כן פתר את החידה.</p>
<p>אני מקווה שלא התעייפתם מהחידות של אריק, אני בטוח לא התעייפתי וממשיך (גם אם לאט) במטרה לסיים יום אחד את כל 25 החידות. הפירסום היום מלווה באופטימיות זהירה שכן אנחנו כמעט בחצי הדרך. בואו נראה מה מחכה לנו ביום 12, איזה פיתרון לא עבד ומה בסוף כן פתר את החידה.</p>
tag:www.tocode.co.il,2005:BlogPost/25522024-02-15T18:00:07+02:002024-02-15T18:00:07+02:00יותר מדי אינפורמציהינון פרקFri, 16 Feb 2024 06:00:00 +0200<p>חבר לומד תכנות לבד מהבית כדי למצוא עבודה. כל פעם שאנחנו מדברים הוא בא עם סיפורים חדשים - </p>
<p>"שמע השבוע התחלתי ללמוד Tensor Flow כי בכל מקום צריכים את זה"</p>
<p>"סיימתי קורס פייתון של 40 שעות וידאו ביודמי - היה מעולה ולמדתי המון"</p>
<p>"תגיד מה דעתך על AWS? התחלתי ללמוד להסמכה של CLF-C01 כי הבנתי שחייבים את זה בשביל להתקבל לעבודה כ MLOps"</p>
<p>ואני מבין את הקושי. אתה לבד, אתה מסתכל על אינסוף חומר שיש באינטרנט, כל כמה ימים אתה פוגש בן אדם אחר שממליץ לך על הדבר שאתה ממש חייב ללמוד כי ככה תמצא עבודה הכי מהר, ואתה לא יודע מה לעשות. אבל הכי גרוע זה שאתה מרגיש שאתה כבר "מבין את זה", זהו סיימת קורס פייתון ראית 40 שעות וידאו ביודמי, בנית 10 פרויקטים מהקורס ופתרת את כל השיעורי בית. אתה בטוח שאתה יודע פייתון אבל עדיין לא מצליח למצוא עבודה. לכן הבעיה חייבת להיות שצריך לדעת עוד דברים.</p>
<p>אבל מעגל הקסמים הזה לא מסתיים.</p>
<p>לא משנה כמה דברים חדשים אתה לומד, אתה מוצא את עצמך באותו מקום. אתה עדיין מחפש עבודה, כל דבר חדש שאתה לומד גורם לך לשכוח קצת מהדבר הקודם, וכל ראיון ממשיכים לבקש ממך לענות על דברים שעדיין לא למדת. מאיפה יש כל כך הרבה דברים בעולם שצריך ללמוד? זה אי פעם ייגמר?</p>
<p>הקושי הוא לראות שעדיין לא סיימת ללמוד אפילו דבר אחד כמו שצריך. שקורס 40 שעות פייתון ופרויקטים של הקורס זה לא מספיק כדי לדעת פייתון. האתגר הוא להבין איך ללמוד את אותו דבר יותר לעומק, ולשלב את הלימוד עם עשייה ובניית פרויקט ברמה גבוהה.</p>
<p>כן גם ב 2024, הדרך הכי מהירה למצוא עבודה היא להגיע עם תיק עבודות ויכולת מוכחת. פרויקט גם עוזר למקד את המאמצים ולהחליט מה צריך ללמוד מתוך אינסוף חומרי הלימוד שברשת, וגם מראה לעולם שאתה יודע להתמקד ולגרום לדברים לקרות.</p>
<p>חבר לומד תכנות לבד מהבית כדי למצוא עבודה. כל פעם שאנחנו מדברים הוא בא עם סיפורים חדשים - </p>
<p>"שמע השבוע התחלתי ללמוד Tensor Flow כי בכל מקום צריכים את זה"</p>
<p>"סיימתי קורס פייתון של 40 שעות וידאו ביודמי - היה מעולה ולמדתי המון"</p>
<p>"תגיד מה דעתך על AWS? התחלתי ללמוד להסמכה של CLF-C01 כי הבנתי שחייבים את זה בשביל להתקבל לעבודה כ MLOps"</p>
<p>ואני מבין את הקושי. אתה לבד, אתה מסתכל על אינסוף חומר שיש באינטרנט, כל כמה ימים אתה פוגש בן אדם אחר שממליץ לך על הדבר שאתה ממש חייב ללמוד כי ככה תמצא עבודה הכי מהר, ואתה לא יודע מה לעשות. אבל הכי גרוע זה שאתה מרגיש שאתה כבר "מבין את זה", זהו סיימת קורס פייתון ראית 40 שעות וידאו ביודמי, בנית 10 פרויקטים מהקורס ופתרת את כל השיעורי בית. אתה בטוח שאתה יודע פייתון אבל עדיין לא מצליח למצוא עבודה. לכן הבעיה חייבת להיות שצריך לדעת עוד דברים.</p>
<p>אבל מעגל הקסמים הזה לא מסתיים.</p>
<p>לא משנה כמה דברים חדשים אתה לומד, אתה מוצא את עצמך באותו מקום. אתה עדיין מחפש עבודה, כל דבר חדש שאתה לומד גורם לך לשכוח קצת מהדבר הקודם, וכל ראיון ממשיכים לבקש ממך לענות על דברים שעדיין לא למדת. מאיפה יש כל כך הרבה דברים בעולם שצריך ללמוד? זה אי פעם ייגמר?</p>
<p>הקושי הוא לראות שעדיין לא סיימת ללמוד אפילו דבר אחד כמו שצריך. שקורס 40 שעות פייתון ופרויקטים של הקורס זה לא מספיק כדי לדעת פייתון. האתגר הוא להבין איך ללמוד את אותו דבר יותר לעומק, ולשלב את הלימוד עם עשייה ובניית פרויקט ברמה גבוהה.</p>
<p>כן גם ב 2024, הדרך הכי מהירה למצוא עבודה היא להגיע עם תיק עבודות ויכולת מוכחת. פרויקט גם עוזר למקד את המאמצים ולהחליט מה צריך ללמוד מתוך אינסוף חומרי הלימוד שברשת, וגם מראה לעולם שאתה יודע להתמקד ולגרום לדברים לקרות.</p>
tag:www.tocode.co.il,2005:BlogPost/25512024-02-14T19:00:08+02:002024-02-14T19:00:08+02:00זה לא מספר השורותינון פרקThu, 15 Feb 2024 06:00:00 +0200<p>מתי פונקציה מפסיקה להיות פונקציה? נניח שיש לנו בעיה שבשביל לפתור אותה אנחנו צריכים להוציא ממחרוזת רשימה של כל הספרות שבה. אולי אנחנו לא יודעים עדיין ביטויים רגולאריים ורק התחלנו ללמוד פייתון וחושבים להשתמש בלולאה, ולכן נכתוב את הפונקציה:</p>
<pre><code class="language-python">def to_list_of_digits(s: str) -> list[int]:
result = []
for ch in s:
if ch.isdigit():
result.append(int(ch))
return result
</code></pre>
<p>וזה עובד! אבל אז אנחנו מגלים שבעצם בפייתון יש מנגנון שנקרא List Comprehension ושאנחנו יכולים לכתוב את הפונקציה בצורה הרבה יותר קצרה:</p>
<pre><code class="language-python">def to_list_of_digits(s: str) -> list[int]:
return [int(ch) for ch in s if ch.isdigit()]
</code></pre>
<p>עכשיו השאלה - האם נישאר עם הפונקציה? אולי עדיף לקחת את השורה האחת ופשוט לשים אותה במקום הקריאה? מי החליט שצריכה להיות כזאת פונקציה בכלל? ואולי אם הייתי מראש יודע על List Comprehension לא הייתי כותב את זה כפונקציה?</p>
<p>התשובה מורכבת אבל כדאי להשאיר בראש כמה נקודות-</p>
<ol>
<li><p>זה לא כמות השורות. מה שהופך פונקציה לרעיון טוב הוא שהפונקציה מספרת סיפור. היא עוזרת לנו לקרוא את הקוד. פונקציה נותנת שם לפעולה מסוימת. אם השם הזה היה הגיוני כשהיא היתה ארוכה יש סיכוי טוב שהוא עדיין הגיוני, גם כשהיא לוקחת שורה אחת.</p></li>
<li><p>פונקציה מאפשרת נקודת בדיקה ומשהו לדון עליו. אני יכול להסתכל על הפונקציה שמושכת ספרות ממחרוזת ולבדוק אם היא עובדת על מחרוזות מסוימות שאני מכיר, או להתלבט מה היא צריכה לעשות במקרי קצה.</p></li>
<li><p>שימוש חוזר בפונקציה מספק הזדמנות לשינוי קל יותר - אם מחר נצטרך להחליף בכל מקום במערכת את ההתנהגות, למשל כדי למשוך מספרים מלאים במקום ספרות, הפונקציה תאפשר לעשות את זה במהירות ותוך שינוי של מקום אחד. העתקת שורת הקוד ושכפולה בקוד, אפילו אם זה רק שורה, גורמת לשינוי להיות יותר מסובך.</p></li>
</ol>
<p>התרחיש של פונקציות ארוכות מדי בקוד הוא הרבה יותר נפוץ מקוד עם פונקציות קצרות מדי. רוב הזמן הנטייה הטבעית שלנו היא לא לייצר פונקציות גם כשצריך אותן. לכן לא הייתי ממהר למחוק פונקציות, גם אם קיצרנו אותן לשורה או שתיים.</p>
<p>מתי פונקציה מפסיקה להיות פונקציה? נניח שיש לנו בעיה שבשביל לפתור אותה אנחנו צריכים להוציא ממחרוזת רשימה של כל הספרות שבה. אולי אנחנו לא יודעים עדיין ביטויים רגולאריים ורק התחלנו ללמוד פייתון וחושבים להשתמש בלולאה, ולכן נכתוב את הפונקציה:</p>
<pre><code class="language-python">def to_list_of_digits(s: str) -> list[int]:
result = []
for ch in s:
if ch.isdigit():
result.append(int(ch))
return result
</code></pre>
<p>וזה עובד! אבל אז אנחנו מגלים שבעצם בפייתון יש מנגנון שנקרא List Comprehension ושאנחנו יכולים לכתוב את הפונקציה בצורה הרבה יותר קצרה:</p>
<pre><code class="language-python">def to_list_of_digits(s: str) -> list[int]:
return [int(ch) for ch in s if ch.isdigit()]
</code></pre>
<p>עכשיו השאלה - האם נישאר עם הפונקציה? אולי עדיף לקחת את השורה האחת ופשוט לשים אותה במקום הקריאה? מי החליט שצריכה להיות כזאת פונקציה בכלל? ואולי אם הייתי מראש יודע על List Comprehension לא הייתי כותב את זה כפונקציה?</p>
<p>התשובה מורכבת אבל כדאי להשאיר בראש כמה נקודות-</p>
<ol>
<li><p>זה לא כמות השורות. מה שהופך פונקציה לרעיון טוב הוא שהפונקציה מספרת סיפור. היא עוזרת לנו לקרוא את הקוד. פונקציה נותנת שם לפעולה מסוימת. אם השם הזה היה הגיוני כשהיא היתה ארוכה יש סיכוי טוב שהוא עדיין הגיוני, גם כשהיא לוקחת שורה אחת.</p></li>
<li><p>פונקציה מאפשרת נקודת בדיקה ומשהו לדון עליו. אני יכול להסתכל על הפונקציה שמושכת ספרות ממחרוזת ולבדוק אם היא עובדת על מחרוזות מסוימות שאני מכיר, או להתלבט מה היא צריכה לעשות במקרי קצה.</p></li>
<li><p>שימוש חוזר בפונקציה מספק הזדמנות לשינוי קל יותר - אם מחר נצטרך להחליף בכל מקום במערכת את ההתנהגות, למשל כדי למשוך מספרים מלאים במקום ספרות, הפונקציה תאפשר לעשות את זה במהירות ותוך שינוי של מקום אחד. העתקת שורת הקוד ושכפולה בקוד, אפילו אם זה רק שורה, גורמת לשינוי להיות יותר מסובך.</p></li>
</ol>
<p>התרחיש של פונקציות ארוכות מדי בקוד הוא הרבה יותר נפוץ מקוד עם פונקציות קצרות מדי. רוב הזמן הנטייה הטבעית שלנו היא לא לייצר פונקציות גם כשצריך אותן. לכן לא הייתי ממהר למחוק פונקציות, גם אם קיצרנו אותן לשורה או שתיים.</p>
tag:www.tocode.co.il,2005:BlogPost/25502024-02-13T20:00:08+02:002024-02-13T20:00:08+02:00מתי בכל זאת לתרום לפרויקט קוד פתוח?ינון פרקWed, 14 Feb 2024 06:00:00 +0200<p>הדיון על תרומות לפרויקטי קוד פתוח התעורר מחדש לאחרונה בעקבות <a target="_blank" href="https://github.com/expressjs/express/pulls?q=is%3Apr+is%3Aclosed">מבול</a> התרומות הפיקטיביות שנשלחו לפרויקט Express. מדובר במדריך וידאו שפורסם בערוץ יוטיוב פופולרי במיוחד שהסביר איך לשלוח PR ויצא משליטה.</p>
<p>אבל למה בעצם אנחנו טועים בסיפור הזה כל פעם מחדש? למה כל כך קשה לשלוח תרומה בעלת משמעות לפרויקט שאנחנו אוהבים? ואיך כן לתרום לפרויקטי קוד פתוח?</p>
<p>הדבר החשוב לזכור הוא שתרומה לפרויקטי קוד פתוח היא כשלעצמה לא מטרה, כשם שפרופיל גיטהאב עם עשרות או מאות מאגרים אינו המטרה. מה כן? תרומה לפרויקטי קוד פתוח היא בעלת ערך במקרים הבאים-</p>
<ol>
<li><p>כשהיא באה מתוך היכרות מעמיקה עם הפרויקט והמגבלות שלו.</p></li>
<li><p>כשהיא באה מתוך כוונה אמיתית לשפר את מצב העניינים באקוסיסטם של הפרויקט.</p></li>
</ol>
<p>תרומה כזאת לפרויקט שאנשים משתמשים בו יכולה גם לשפר את המצב של התורם מבחינת תעסוקה. מי שתורם קוד משמעותי ומועיל ל numpy כנראה יודע די טוב numpy ולכן אולי שווה לי לגייס אותו לצוות. התרומה לפרויקט במקרה הזה היא אינדיקציה לרמת היכרות מעמיקה עם המערכת והקהילה. לאף אחד לא אכפת שאתם יודעים להשתמש בכפתור כדי לשלוח PR. מה שחשוב כדי לקבל עבודה הוא היכרות מעמיקה עם פרויקט, יכולת עבודה בצוות, הבנה של הבעיות והאילוצים של הפרויקט ויצירת קוד חדש בתוך אותם אילוצים.</p>
<p>הדיון על תרומות לפרויקטי קוד פתוח התעורר מחדש לאחרונה בעקבות <a target="_blank" href="https://github.com/expressjs/express/pulls?q=is%3Apr+is%3Aclosed">מבול</a> התרומות הפיקטיביות שנשלחו לפרויקט Express. מדובר במדריך וידאו שפורסם בערוץ יוטיוב פופולרי במיוחד שהסביר איך לשלוח PR ויצא משליטה.</p>
<p>אבל למה בעצם אנחנו טועים בסיפור הזה כל פעם מחדש? למה כל כך קשה לשלוח תרומה בעלת משמעות לפרויקט שאנחנו אוהבים? ואיך כן לתרום לפרויקטי קוד פתוח?</p>
<p>הדבר החשוב לזכור הוא שתרומה לפרויקטי קוד פתוח היא כשלעצמה לא מטרה, כשם שפרופיל גיטהאב עם עשרות או מאות מאגרים אינו המטרה. מה כן? תרומה לפרויקטי קוד פתוח היא בעלת ערך במקרים הבאים-</p>
<ol>
<li><p>כשהיא באה מתוך היכרות מעמיקה עם הפרויקט והמגבלות שלו.</p></li>
<li><p>כשהיא באה מתוך כוונה אמיתית לשפר את מצב העניינים באקוסיסטם של הפרויקט.</p></li>
</ol>
<p>תרומה כזאת לפרויקט שאנשים משתמשים בו יכולה גם לשפר את המצב של התורם מבחינת תעסוקה. מי שתורם קוד משמעותי ומועיל ל numpy כנראה יודע די טוב numpy ולכן אולי שווה לי לגייס אותו לצוות. התרומה לפרויקט במקרה הזה היא אינדיקציה לרמת היכרות מעמיקה עם המערכת והקהילה. לאף אחד לא אכפת שאתם יודעים להשתמש בכפתור כדי לשלוח PR. מה שחשוב כדי לקבל עבודה הוא היכרות מעמיקה עם פרויקט, יכולת עבודה בצוות, הבנה של הבעיות והאילוצים של הפרויקט ויצירת קוד חדש בתוך אותם אילוצים.</p>
tag:www.tocode.co.il,2005:BlogPost/25492024-02-12T20:00:07+02:002024-02-12T20:00:07+02:00טיפ פייטסט: איך ועל איזה בדיקות לדלג?ינון פרקTue, 13 Feb 2024 06:00:00 +0200<p>אחד הפיצ'רים החמודים של פייטסט הוא היכולת "לסמן" בדיקות בכל דרך שתבחרו. יוצרים קובץ בשם pytest.ini עם תוכן שנראה בערך ככה:</p>
<pre><code class="language-ini">[pytest]
markers =
integration: integration test
slow: slow test
version: tests to run before deploying a new version
</code></pre>
<p>ועכשיו אפשר לסמן בדיקה מאחת הקטגוריות עם השטרודל המתאים למשל:</p>
<pre><code class="language-python">@pytest.mark.integration
def test_website():
pass
</code></pre>
<p>ולהריץ את כל הבדיקות שמסומנות בקטגוריה integration:</p>
<pre><code class="language-sh">$ pytest -m integration
</code></pre>
<p>או להריץ את כל הדברים שלא מהקטגוריה עם:</p>
<pre><code class="language-sh">$ pytest -m "not integration"
</code></pre>
<p>פייטסט גם כולל המון סימונים מובנים למשל הסימון skip שגורם לפייטסט לדלג על בדיקה, skipif שמקבל תנאי וגורם לדילוג על בדיקה רק אם התנאי מתקיים ו xfail שמסמן שבדיקה צריכה להיכשל (ולכן לא צריך להתרגש מכישלון).</p>
<p>אבל האתגר היותר משמעותי הוא לא איך להשתמש בפיצ'ר אלא מתי להשתמש בו - כלומר על איזה בדיקות כדאי לדלג ואיזה קטגוריות להגדיר, מתי להוסיף xfail ומתי skip ומתי בכלל עדיף למחוק את הבדיקה.</p>
<p>ננסה לענות על זה בכמה כללי אצבע-</p>
<ol>
<li><p>רוב הפיצ'רים של בדיקות הם יותר טובים כשלא משתמשים בהם. זה נכון לגבי mock-ים, לגבי before ו after וכן גם לגבי דילוגים. אם אתם יכולים בלי זה עדיף.</p></li>
<li><p>לפעמים זה נוח להגדיר סט מסוים של בדיקות שצריכות רכיב תשתית כדי לעבוד. לדוגמה בדיקות אינטגרציה שצריכות לעבוד מול בסיס נתונים ולפני שמפעילים אותן צריך להעלות קונטיינר של בסיס הנתונים. אז נגיד את האמת הכי טוב לדאוג שהבדיקות יפעילו לעצמן את הקונטיינר של בסיס הנתונים או ישתמשו בגירסת In Memory של בסיס הנתונים, אבל לא תמיד זה אפשרי. במצבים כאלה שווה לסמן את הבדיקות שצריכות שנעשה משהו לפני כדי שאפשר יהיה לפעמים לדלג עליהן.</p></li>
<li><p>לפעמים יש בדיקות שנכשלות מדי פעם אבל כרגע אין לנו זמן לבדוק למה. ברוב מוחלט של המקרים כדאי למחוק את הבדיקות האלה כי אם המוצר עובד כמו שצריך והבדיקה לפעמים נכשלת אז כנראה שיש בעיה בבדיקה או שהיא בודקת מסלולים לא רלוונטיים. ובכל זאת אולי יש איזה ערך סנטימנטלי לבדיקה או סיבה אחרת להשאיר את הקוד, ואז נוח לראות שיש בדיקה כזאת למרות שכרגע היא לא עובדת. אני כן חייב להודות שמהניסיון שלי נדיר מאוד שמישהו מוצא זמן לתקן בדיקה שהיתה ב skip.</p></li>
<li><p>בדף התיעוד יש דוגמה ל skipif שמדלגת על בדיקה לפי מערכת הפעלה. אישית כשאני כותב בדיקה שצריכה לרוץ רק על מערכת הפעלה מסוימת אני אעדיף לא לראות אותה ב skip או ב xfail כי המשמעות של סימונים אלה היא בדרך כלל שיש איזה בעיה בבדיקה. במקום זה הייתי בקוד הבדיקה מוסיף את הבדיקה ומסמן "הצלחה" אם זאת לא מערכת ההפעלה המתאימה.</p></li>
</ol>
<p>אלה הטיפים שלי לדילוגים, אם גם לכם יש שיטות שעוזרות להסתדר עם דילוגים מוזמנים לשתף בתגובות או בטלגרם.</p>
<p>אחד הפיצ'רים החמודים של פייטסט הוא היכולת "לסמן" בדיקות בכל דרך שתבחרו. יוצרים קובץ בשם pytest.ini עם תוכן שנראה בערך ככה:</p>
<pre><code class="language-ini">[pytest]
markers =
integration: integration test
slow: slow test
version: tests to run before deploying a new version
</code></pre>
<p>ועכשיו אפשר לסמן בדיקה מאחת הקטגוריות עם השטרודל המתאים למשל:</p>
<pre><code class="language-python">@pytest.mark.integration
def test_website():
pass
</code></pre>
<p>ולהריץ את כל הבדיקות שמסומנות בקטגוריה integration:</p>
<pre><code class="language-sh">$ pytest -m integration
</code></pre>
<p>או להריץ את כל הדברים שלא מהקטגוריה עם:</p>
<pre><code class="language-sh">$ pytest -m "not integration"
</code></pre>
<p>פייטסט גם כולל המון סימונים מובנים למשל הסימון skip שגורם לפייטסט לדלג על בדיקה, skipif שמקבל תנאי וגורם לדילוג על בדיקה רק אם התנאי מתקיים ו xfail שמסמן שבדיקה צריכה להיכשל (ולכן לא צריך להתרגש מכישלון).</p>
<p>אבל האתגר היותר משמעותי הוא לא איך להשתמש בפיצ'ר אלא מתי להשתמש בו - כלומר על איזה בדיקות כדאי לדלג ואיזה קטגוריות להגדיר, מתי להוסיף xfail ומתי skip ומתי בכלל עדיף למחוק את הבדיקה.</p>
<p>ננסה לענות על זה בכמה כללי אצבע-</p>
<ol>
<li><p>רוב הפיצ'רים של בדיקות הם יותר טובים כשלא משתמשים בהם. זה נכון לגבי mock-ים, לגבי before ו after וכן גם לגבי דילוגים. אם אתם יכולים בלי זה עדיף.</p></li>
<li><p>לפעמים זה נוח להגדיר סט מסוים של בדיקות שצריכות רכיב תשתית כדי לעבוד. לדוגמה בדיקות אינטגרציה שצריכות לעבוד מול בסיס נתונים ולפני שמפעילים אותן צריך להעלות קונטיינר של בסיס הנתונים. אז נגיד את האמת הכי טוב לדאוג שהבדיקות יפעילו לעצמן את הקונטיינר של בסיס הנתונים או ישתמשו בגירסת In Memory של בסיס הנתונים, אבל לא תמיד זה אפשרי. במצבים כאלה שווה לסמן את הבדיקות שצריכות שנעשה משהו לפני כדי שאפשר יהיה לפעמים לדלג עליהן.</p></li>
<li><p>לפעמים יש בדיקות שנכשלות מדי פעם אבל כרגע אין לנו זמן לבדוק למה. ברוב מוחלט של המקרים כדאי למחוק את הבדיקות האלה כי אם המוצר עובד כמו שצריך והבדיקה לפעמים נכשלת אז כנראה שיש בעיה בבדיקה או שהיא בודקת מסלולים לא רלוונטיים. ובכל זאת אולי יש איזה ערך סנטימנטלי לבדיקה או סיבה אחרת להשאיר את הקוד, ואז נוח לראות שיש בדיקה כזאת למרות שכרגע היא לא עובדת. אני כן חייב להודות שמהניסיון שלי נדיר מאוד שמישהו מוצא זמן לתקן בדיקה שהיתה ב skip.</p></li>
<li><p>בדף התיעוד יש דוגמה ל skipif שמדלגת על בדיקה לפי מערכת הפעלה. אישית כשאני כותב בדיקה שצריכה לרוץ רק על מערכת הפעלה מסוימת אני אעדיף לא לראות אותה ב skip או ב xfail כי המשמעות של סימונים אלה היא בדרך כלל שיש איזה בעיה בבדיקה. במקום זה הייתי בקוד הבדיקה מוסיף את הבדיקה ומסמן "הצלחה" אם זאת לא מערכת ההפעלה המתאימה.</p></li>
</ol>
<p>אלה הטיפים שלי לדילוגים, אם גם לכם יש שיטות שעוזרות להסתדר עם דילוגים מוזמנים לשתף בתגובות או בטלגרם.</p>
tag:www.tocode.co.il,2005:BlogPost/25482024-02-11T16:00:07+02:002024-02-11T16:00:07+02:00זה צריך לקחת שבועייםינון פרקMon, 12 Feb 2024 06:00:00 +0200<p>גם בשיחה על זמנים כדאי להיות ברורים. כשאתה אומר "זה צריך לקחת שבועיים" הכוונה ש-</p>
<ol>
<li><p>לא בדקתי, אבל נראה לי שזה מה שהיה לוקח לי.</p></li>
<li><p>לא בדקתי, אבל נראה לי שאני הייתי מסיים את זה בשבוע ולקחתי מרווח ביטחון.</p></li>
<li><p>אני מוכן לעבוד רק עם מפתחים שמצליחים לסיים משימה כזאת בשבועיים. אם זה ייקח לך יותר כנראה שיש לך בעיה.</p></li>
<li><p>הדד ליין שלנו כלפי לקוח חיצוני דורש שנסיים את זה בשבועיים. אם יש בעיה חשוב להרים דגל כמה שיותר מהר ונרדד חלקים מהפיצ'ר.</p></li>
<li><p>בעיקרון זאת משימה של יומיים אבל אני משאיר לך זמן לריפקטורינג כי התשתית של המערכת לא בנויה לתמוך במנגנון הזה.</p></li>
<li><p>בעיקרון זאת משימה של יומיים אבל אני משאיר לך זמן למידה כי אני יודע שלא עבדת עם החלקים האלה במערכת או עם הטכנולוגיה הזאת בעבר.</p></li>
<li><p>ישבתי כבר חודשיים לעשות Research על המשימה וגיליתי דרך מהירה לפתור את האתגר. אני יודע שזה נראה מסובך אבל אחרי שנשב זה יראה ממש פשוט.</p></li>
</ol>
<p>אם אתם בצד שנותן הוראות נסו להיות כמה שפחות מעורפלים. הבן אדם שמולכם לא קורא מחשבות ואתם יכולים לחסוך הרבה מתח לשני הצדדים אם תהיו ברורים. ואם אתם בצד שמקבל את ההוראה אל תתביישו לשאול. רוב הסיכויים שהבן אדם שמולכם רוצה בטובתכם. חסכו לכולם אי הבנה ותשאלו גם אם נראה לכם שהבנתם בדיוק מה קורה שם.</p>
<p>גם בשיחה על זמנים כדאי להיות ברורים. כשאתה אומר "זה צריך לקחת שבועיים" הכוונה ש-</p>
<ol>
<li><p>לא בדקתי, אבל נראה לי שזה מה שהיה לוקח לי.</p></li>
<li><p>לא בדקתי, אבל נראה לי שאני הייתי מסיים את זה בשבוע ולקחתי מרווח ביטחון.</p></li>
<li><p>אני מוכן לעבוד רק עם מפתחים שמצליחים לסיים משימה כזאת בשבועיים. אם זה ייקח לך יותר כנראה שיש לך בעיה.</p></li>
<li><p>הדד ליין שלנו כלפי לקוח חיצוני דורש שנסיים את זה בשבועיים. אם יש בעיה חשוב להרים דגל כמה שיותר מהר ונרדד חלקים מהפיצ'ר.</p></li>
<li><p>בעיקרון זאת משימה של יומיים אבל אני משאיר לך זמן לריפקטורינג כי התשתית של המערכת לא בנויה לתמוך במנגנון הזה.</p></li>
<li><p>בעיקרון זאת משימה של יומיים אבל אני משאיר לך זמן למידה כי אני יודע שלא עבדת עם החלקים האלה במערכת או עם הטכנולוגיה הזאת בעבר.</p></li>
<li><p>ישבתי כבר חודשיים לעשות Research על המשימה וגיליתי דרך מהירה לפתור את האתגר. אני יודע שזה נראה מסובך אבל אחרי שנשב זה יראה ממש פשוט.</p></li>
</ol>
<p>אם אתם בצד שנותן הוראות נסו להיות כמה שפחות מעורפלים. הבן אדם שמולכם לא קורא מחשבות ואתם יכולים לחסוך הרבה מתח לשני הצדדים אם תהיו ברורים. ואם אתם בצד שמקבל את ההוראה אל תתביישו לשאול. רוב הסיכויים שהבן אדם שמולכם רוצה בטובתכם. חסכו לכולם אי הבנה ותשאלו גם אם נראה לכם שהבנתם בדיוק מה קורה שם.</p>
tag:www.tocode.co.il,2005:BlogPost/25472024-02-10T20:00:07+02:002024-02-10T20:00:07+02:00פיתרון Advent Of Code 2023 יום 11 חלק 1 בסקאלהינון פרקSun, 11 Feb 2024 06:00:00 +0200<p>אני ממשיך להתקדם לאט עם האתגר של אריק ווסטל אבל זה בסדר כבר אמרנו שיש עד דצמבר הבא עד סט החידות החדש והנה כבר הגענו ליום 11. בואו נראה יחד את התרגיל ואת הפיתרון שלי בסקאלה, ואתם מוזמנים להציע תרגומים של הפיתרון לשפות אחרות או פיתרונות חלופיים וטובים יותר.</p>
<p>אני ממשיך להתקדם לאט עם האתגר של אריק ווסטל אבל זה בסדר כבר אמרנו שיש עד דצמבר הבא עד סט החידות החדש והנה כבר הגענו ליום 11. בואו נראה יחד את התרגיל ואת הפיתרון שלי בסקאלה, ואתם מוזמנים להציע תרגומים של הפיתרון לשפות אחרות או פיתרונות חלופיים וטובים יותר.</p>
tag:www.tocode.co.il,2005:BlogPost/25462024-02-09T15:00:07+02:002024-02-09T15:00:07+02:00דברים קטניםינון פרקSat, 10 Feb 2024 06:00:00 +0200<p>כשלא מבינים, גם דברים קטנים נראים מלחיצים. הם גורמים לאנשים טובים להיבהל ולחפש פיתרונות מהירים, רק בשביל להבטיח לעצמנו שהבעיה לא גדולה כמו שזה נראה-</p>
<p>״למה הקונטיינר שלי לא נדלק?!״</p>
<p>״למה אין עדיין רווחים מהמערכת?!״</p>
<p>״למה הדף נטען כל כך לאט?!״</p>
<p>לפעמים הפיתרון באמת ייקח דקה. לפעמים יותר. הדבר החשוב הוא לשים לב שככל שמבינים יותר כך נלחצים פחות, ויודעים יותר טוב מה לעשות. בהתמודדות עם אתגר לא מוכר שווה לבחור באחת משתי דרכים:</p>
<ol>
<li><p>ללמוד את עולם התוכן ברמה מספיק טובה כדי להתמודד עם האתגר הזה.</p></li>
<li><ul>
<li>או - לשלם למישהו שלמד כבר את עולם התוכן כדי שיעזור לנו לפתור את האתגר.</li>
</ul></li>
</ol>
<p>הטעות שכדאי להיזהר ממנה היא ללמוד רק קצת מתוך עולם התוכן "מספיק בשביל לפתור את הבעיה" ואז לפתור דברים עקום. התוצאה בדרך כלל תהיה שהבעיה תישאר ורק תיראה עוד יותר מסובכת.</p>
<p>כשלא מבינים, גם דברים קטנים נראים מלחיצים. הם גורמים לאנשים טובים להיבהל ולחפש פיתרונות מהירים, רק בשביל להבטיח לעצמנו שהבעיה לא גדולה כמו שזה נראה-</p>
<p>״למה הקונטיינר שלי לא נדלק?!״</p>
<p>״למה אין עדיין רווחים מהמערכת?!״</p>
<p>״למה הדף נטען כל כך לאט?!״</p>
<p>לפעמים הפיתרון באמת ייקח דקה. לפעמים יותר. הדבר החשוב הוא לשים לב שככל שמבינים יותר כך נלחצים פחות, ויודעים יותר טוב מה לעשות. בהתמודדות עם אתגר לא מוכר שווה לבחור באחת משתי דרכים:</p>
<ol>
<li><p>ללמוד את עולם התוכן ברמה מספיק טובה כדי להתמודד עם האתגר הזה.</p></li>
<li><ul>
<li>או - לשלם למישהו שלמד כבר את עולם התוכן כדי שיעזור לנו לפתור את האתגר.</li>
</ul></li>
</ol>
<p>הטעות שכדאי להיזהר ממנה היא ללמוד רק קצת מתוך עולם התוכן "מספיק בשביל לפתור את הבעיה" ואז לפתור דברים עקום. התוצאה בדרך כלל תהיה שהבעיה תישאר ורק תיראה עוד יותר מסובכת.</p>
tag:www.tocode.co.il,2005:BlogPost/25442024-02-08T19:00:07+02:002024-02-08T19:00:07+02:00סוגרייםינון פרקFri, 09 Feb 2024 06:00:00 +0200<p>אז כמו תמיד אחרי שאני מבלה יותר מדי זמן בלדבג משהו טפשי אני הולך לכתוב את זה כאן כדי לא לשכוח וכדי שלפחות נלמד משהו מהסיפור, והיום אנחנו רוצים לדבר על סוגריים וסקאלה. נו, רוצים זאת מילה גדולה, אף אחד לא רוצה לדבר על סוגריים. ובכל זאת לפעמים צריך ובמיוחד כשסוגריים יכולים להיות מבלבלים. נתחיל בקוד-</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}.recover { err => 30 }
println(result)
</code></pre>
<p>והשאלה מה הערך של result?</p>
<p>בשביל לענות על זה נתחיל עם דוגמת קוד יותר פשוטה ונוריד את ה recover. עכשיו בלוק ה then ובלוק ה else שקולים:</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}
</code></pre>
<p>ו result יהיה שווה לאוביקט Failure שה Exception שלו תלויה בתוצאה של החישוב האקראי. פקודת recover בסקאלה שמופעלת על אוביקט Try משנה אותו מ Failure ל Success עם הערך שמופיע ב recover. הדוגמה הבאה לכן גם צפויה:</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}
println(result.recover { err => 30 })
</code></pre>
<p>בגלל שלא משנה מה הוגרל result החזיק בכל מקרה Failure, אז הפעלת recover תשנה את הערך ל Success של 30 וזה מה שיודפס:</p>
<pre><code class="language-text">Success(30)
</code></pre>
<p>אבל מה עם קטע הקוד הראשון שהדבקתי? עכשיו אנחנו מבינים מה רציתי שהוא יעשה - רציתי שהוא יפעיל את recover על הערך שחזר מה if, לפני שנשמר ב result. זה לא מה שקרה. בשביל להבין מה כן קרה צריך קודם כל למחוק את הסוגריים המסולסלים:</p>
<pre><code class="language-scala"> val result = if (Random.nextInt() > 0)
Try { throw new Exception("20") }
else Try { throw new Exception("30")
</code></pre>
<p>כדאי לחשוב על ה"בלוק" בסקאלה בתור פרמטר ל if או ל else. לכן אין בעיה לכתוב את השורה בלעדיו ולכן הגירסה הראשונה של הקוד שקולה לגירסה הזו:</p>
<pre><code class="language-scala">val result = if (Random.nextInt() > 0)
Try { throw new Exception("20") }
else
Try { throw new Exception("30") }.recover { _ => 30 }
</code></pre>
<p>עכשיו זה ברור - ה recover השפיע רק על בלוק ה else ולא על כל ה if. הקוד החזיר Success כשהמספר האקראי היה 0 או שלילי, ובמספרים חיוביים החזיר Failure. כשמבינים איך זה עובד קל גם לתקן:</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = (if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}).recover { _ => 30 }
println(result.recover { err => 30 })
</code></pre>
<p>אז כמו תמיד אחרי שאני מבלה יותר מדי זמן בלדבג משהו טפשי אני הולך לכתוב את זה כאן כדי לא לשכוח וכדי שלפחות נלמד משהו מהסיפור, והיום אנחנו רוצים לדבר על סוגריים וסקאלה. נו, רוצים זאת מילה גדולה, אף אחד לא רוצה לדבר על סוגריים. ובכל זאת לפעמים צריך ובמיוחד כשסוגריים יכולים להיות מבלבלים. נתחיל בקוד-</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}.recover { err => 30 }
println(result)
</code></pre>
<p>והשאלה מה הערך של result?</p>
<p>בשביל לענות על זה נתחיל עם דוגמת קוד יותר פשוטה ונוריד את ה recover. עכשיו בלוק ה then ובלוק ה else שקולים:</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}
</code></pre>
<p>ו result יהיה שווה לאוביקט Failure שה Exception שלו תלויה בתוצאה של החישוב האקראי. פקודת recover בסקאלה שמופעלת על אוביקט Try משנה אותו מ Failure ל Success עם הערך שמופיע ב recover. הדוגמה הבאה לכן גם צפויה:</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}
println(result.recover { err => 30 })
</code></pre>
<p>בגלל שלא משנה מה הוגרל result החזיק בכל מקרה Failure, אז הפעלת recover תשנה את הערך ל Success של 30 וזה מה שיודפס:</p>
<pre><code class="language-text">Success(30)
</code></pre>
<p>אבל מה עם קטע הקוד הראשון שהדבקתי? עכשיו אנחנו מבינים מה רציתי שהוא יעשה - רציתי שהוא יפעיל את recover על הערך שחזר מה if, לפני שנשמר ב result. זה לא מה שקרה. בשביל להבין מה כן קרה צריך קודם כל למחוק את הסוגריים המסולסלים:</p>
<pre><code class="language-scala"> val result = if (Random.nextInt() > 0)
Try { throw new Exception("20") }
else Try { throw new Exception("30")
</code></pre>
<p>כדאי לחשוב על ה"בלוק" בסקאלה בתור פרמטר ל if או ל else. לכן אין בעיה לכתוב את השורה בלעדיו ולכן הגירסה הראשונה של הקוד שקולה לגירסה הזו:</p>
<pre><code class="language-scala">val result = if (Random.nextInt() > 0)
Try { throw new Exception("20") }
else
Try { throw new Exception("30") }.recover { _ => 30 }
</code></pre>
<p>עכשיו זה ברור - ה recover השפיע רק על בלוק ה else ולא על כל ה if. הקוד החזיר Success כשהמספר האקראי היה 0 או שלילי, ובמספרים חיוביים החזיר Failure. כשמבינים איך זה עובד קל גם לתקן:</p>
<pre><code class="language-scala"> @main
def parens(): Unit =
val result = (if (Random.nextInt() > 0) {
Try { throw new Exception("20") }
} else {
Try { throw new Exception("30") }
}).recover { _ => 30 }
println(result.recover { err => 30 })
</code></pre>
tag:www.tocode.co.il,2005:BlogPost/25432024-02-07T17:00:07+02:002024-02-07T17:00:07+02:00ואז גיליתיינון פרקThu, 08 Feb 2024 06:00:00 +0200<p>אני כותב עכשיו בוט לטלגרם שכולל הודעות עם כפתורים, למשל דמיינו הודעה על הפוסט הזה שכוללת כפתור "לייק". המימוש מסתבר מחזיר אותנו לאתגר הקלאסי של כפתורים בממשק - איך להוסיף מידע מעניין לאירוע הלחיצה על הכפתור. ואני אסביר.</p>
<p>כשמשתמש לוחץ על כפתור "לייק" מופעלת פונקציה. אותה פונקציה צריכה להבין לאיזה פוסט צריך לסמן את הלייק לפני שתוכל ללכת לעדכן את הפרטים בבסיס הנתונים.</p>
<p>פונקציה שמטפלת בלחיצה על כפתור מקבלת את <a target="_blank" href="https://core.telegram.org/method/messages.getBotCallbackAnswer">הפרמטרים הבאים</a>: <code dir="ltr">flags</code>, <code dir="ltr">game</code>, <code dir="ltr">peer</code>, <code dir="ltr">msg_id</code>, <code dir="ltr">data</code>, <code dir="ltr">password</code>, ולכן הדבר שנראה לי הכי הגיוני כשניגשתי לממש את זה פעם ראשונה היה לשים את המידע החשוב בשדה data של הכפתור, ואז לקבל את המידע הזה בטיפול בלחיצה. בדוגמה של הלייק נשים את ה URL של הפוסט בתור data, נקבל אותו בפונקציה שתטפל בלחיצה כל הכפתור והכל טוב.</p>
<p>(ואז גיליתי)</p>
<p>ופה הסיפור נהיה מעניין כי אחרי שהתחלתי לבדוק את הבוט ראיתי שחלק מהכפתורים עובדים וחלק לא עובדים. המשך מחקר הביא אותי לגילוי העצוב שיש מגבלת אורך על אותו callback data של 64 תווים. עם ה URL-ים הקצרים לא היתה בעיה אבל כשהיה URL ארוך מספיק הקוד לא עבד.</p>
<p>לנקודות האלה של "עכשיו גיליתי" יש פוטנציאל לחשוף משהו מעבר למימוש הספציפי, ויכולת לדחוף אותנו לפיתרונות מהירים שיפגעו בנו בטווח הרחוק. במקרה של הבוט הנה כמה אפשרויות:</p>
<ol>
<li><p>אפשר להישאר עם המבנה של שמירת URL-ים בכפתורים, אבל פשוט לעבור דרך שירות קיצור URL-ים כדי לוודא שכולם קצרים יותר מ 64 תווים.</p></li>
<li><p>אפשר לייצר ב DB מזהה אקראי אחר קצר יותר לכל פוסט, נקרא לו "מזהה קצר בשביל כפתור טלגרם" ולכתוב אותו בתור הערך של ה data.</p></li>
<li><p>אפשר לשים לב לפרמטר נוסף <code dir="ltr">msg_id</code> ברשימת הפרמטרים של טיפול בלחיצה. בעזרת מזהה ההודעה אני יכול להגיע להודעה שכללה את הכפתור ולהסתכל שם מה היה ה URL של הפוסט.</p></li>
<li><p>אפשר ללכת עוד צעד ולשמור ב DB כל הודעה שהבוט שולח, עם מפתח זר לפוסטים. עכשיו כשאני אקבל <code dir="ltr">msg_id</code> אני לא צריך לחפש בהיסטוריית ההודעות בטלגרם ויכול לעשות את הבדיקה אצלי ב DB.</p></li>
</ol>
<p>"ואז גיליתי" זו נקודה שהופכת אירוע שבכלל לא חשבתי עליו כשכתבתי את הקוד למשהו מעניין. ואז גיליתי, ועכשיו אפשר לחשוב קצת יותר לעומק על פיתרון, ועל הריפקטורינג שצריך בשביל להגיע לאותו פיתרון, ואולי לחשוב איך לבנות את זה כדי שיהיה קל לעבור לפיתרון Fallback נוסף אם אני אגלה עוד בעיות. "ואז גיליתי" אלה ההזדמנויות שלנו לשפר את מבנה הקוד כדי להתמודד טוב יותר עם שינויים בעתיד.</p>
<p>אני כותב עכשיו בוט לטלגרם שכולל הודעות עם כפתורים, למשל דמיינו הודעה על הפוסט הזה שכוללת כפתור "לייק". המימוש מסתבר מחזיר אותנו לאתגר הקלאסי של כפתורים בממשק - איך להוסיף מידע מעניין לאירוע הלחיצה על הכפתור. ואני אסביר.</p>
<p>כשמשתמש לוחץ על כפתור "לייק" מופעלת פונקציה. אותה פונקציה צריכה להבין לאיזה פוסט צריך לסמן את הלייק לפני שתוכל ללכת לעדכן את הפרטים בבסיס הנתונים.</p>
<p>פונקציה שמטפלת בלחיצה על כפתור מקבלת את <a target="_blank" href="https://core.telegram.org/method/messages.getBotCallbackAnswer">הפרמטרים הבאים</a>: <code dir="ltr">flags</code>, <code dir="ltr">game</code>, <code dir="ltr">peer</code>, <code dir="ltr">msg_id</code>, <code dir="ltr">data</code>, <code dir="ltr">password</code>, ולכן הדבר שנראה לי הכי הגיוני כשניגשתי לממש את זה פעם ראשונה היה לשים את המידע החשוב בשדה data של הכפתור, ואז לקבל את המידע הזה בטיפול בלחיצה. בדוגמה של הלייק נשים את ה URL של הפוסט בתור data, נקבל אותו בפונקציה שתטפל בלחיצה כל הכפתור והכל טוב.</p>
<p>(ואז גיליתי)</p>
<p>ופה הסיפור נהיה מעניין כי אחרי שהתחלתי לבדוק את הבוט ראיתי שחלק מהכפתורים עובדים וחלק לא עובדים. המשך מחקר הביא אותי לגילוי העצוב שיש מגבלת אורך על אותו callback data של 64 תווים. עם ה URL-ים הקצרים לא היתה בעיה אבל כשהיה URL ארוך מספיק הקוד לא עבד.</p>
<p>לנקודות האלה של "עכשיו גיליתי" יש פוטנציאל לחשוף משהו מעבר למימוש הספציפי, ויכולת לדחוף אותנו לפיתרונות מהירים שיפגעו בנו בטווח הרחוק. במקרה של הבוט הנה כמה אפשרויות:</p>
<ol>
<li><p>אפשר להישאר עם המבנה של שמירת URL-ים בכפתורים, אבל פשוט לעבור דרך שירות קיצור URL-ים כדי לוודא שכולם קצרים יותר מ 64 תווים.</p></li>
<li><p>אפשר לייצר ב DB מזהה אקראי אחר קצר יותר לכל פוסט, נקרא לו "מזהה קצר בשביל כפתור טלגרם" ולכתוב אותו בתור הערך של ה data.</p></li>
<li><p>אפשר לשים לב לפרמטר נוסף <code dir="ltr">msg_id</code> ברשימת הפרמטרים של טיפול בלחיצה. בעזרת מזהה ההודעה אני יכול להגיע להודעה שכללה את הכפתור ולהסתכל שם מה היה ה URL של הפוסט.</p></li>
<li><p>אפשר ללכת עוד צעד ולשמור ב DB כל הודעה שהבוט שולח, עם מפתח זר לפוסטים. עכשיו כשאני אקבל <code dir="ltr">msg_id</code> אני לא צריך לחפש בהיסטוריית ההודעות בטלגרם ויכול לעשות את הבדיקה אצלי ב DB.</p></li>
</ol>
<p>"ואז גיליתי" זו נקודה שהופכת אירוע שבכלל לא חשבתי עליו כשכתבתי את הקוד למשהו מעניין. ואז גיליתי, ועכשיו אפשר לחשוב קצת יותר לעומק על פיתרון, ועל הריפקטורינג שצריך בשביל להגיע לאותו פיתרון, ואולי לחשוב איך לבנות את זה כדי שיהיה קל לעבור לפיתרון Fallback נוסף אם אני אגלה עוד בעיות. "ואז גיליתי" אלה ההזדמנויות שלנו לשפר את מבנה הקוד כדי להתמודד טוב יותר עם שינויים בעתיד.</p>
tag:www.tocode.co.il,2005:BlogPost/25422024-02-06T17:00:07+02:002024-02-06T17:00:07+02:00פוסט אורח - בניית זחלן רשת Crawling Engineינון פרקWed, 07 Feb 2024 06:00:00 +0200<p>הכותב הוא <a target="_blank" href="https://www.linkedin.com/in/valentine-oragbakosi-270604120/">Oragbakosi Valentine</a>, בשלוש השנים האחרונות הוא עובד כמפתח תוכנה ב<a target="_blank" href="https://gitstart.com/">גיטסטארט</a> ולפני כן עבד בGoSquare.</p>
<p><a target="_blank" href="https://gitstart.com/">גיטסטארט</a> היא פלטפורמת Code as a Service שהופכת את מה יש לכם בבקלוג (backlog) לקוד באיכות גבוהה ובו זמנית מטפחת קהילה הולכת וגדלה של מפתחים ברחבי העולם. בתחילת השנה השיקה <a target="_blank" href="https://gitstart.com/">גיטסטארט</a> את פעילותה בישראל והחלה לתמוך בצוותי פיתוח מקומיים.</p>
<p>הכותב הוא <a target="_blank" href="https://www.linkedin.com/in/valentine-oragbakosi-270604120/">Oragbakosi Valentine</a>, בשלוש השנים האחרונות הוא עובד כמפתח תוכנה ב<a target="_blank" href="https://gitstart.com/">גיטסטארט</a> ולפני כן עבד בGoSquare.</p>
<p><a target="_blank" href="https://gitstart.com/">גיטסטארט</a> היא פלטפורמת Code as a Service שהופכת את מה יש לכם בבקלוג (backlog) לקוד באיכות גבוהה ובו זמנית מטפחת קהילה הולכת וגדלה של מפתחים ברחבי העולם. בתחילת השנה השיקה <a target="_blank" href="https://gitstart.com/">גיטסטארט</a> את פעילותה בישראל והחלה לתמוך בצוותי פיתוח מקומיים.</p>
tag:www.tocode.co.il,2005:BlogPost/25412024-02-05T18:00:08+02:002024-02-05T18:00:08+02:00משחקים עם חישוב מקבילי בסקאלהינון פרקTue, 06 Feb 2024 06:00:00 +0200<p>אחת הבעיות של עבודה לא מבוססת Java בתוך ה JVM היא שיש יותר מדי דרכים לעשות דברים, ולא תמיד ברור במה לבחור. במקרה של סקאלה ומקביליות זה נהיה מסובך כי יש גם שיקולים של ארכיטקטורה ותכנות פונקציונאלי.</p>
<p>בשביל המשחק רציתי לספור כמה מספרים ראשוניים יש עד 10 מיליון, ולפצל את החישוב לכמה תהליכונים תוך שימוש בשתי גישות פשוטות למקביליות ובהשוואה עם חישוב סדרתי.</p>
<p>הגישה המקבילית הראשונה היתה פשוט לפתוח Future לכל מספר כדי לזהות אם הוא ראשוני, ולתת ל Java לשגר את התהליכונים.</p>
<p>הגישה המקבילית השניה היתה הספריה parallel-collections שמציעה מימוש של map מקבילי.</p>
<p>וכן נצטרך לעשות פוסט המשך עם cats-effect.</p>
<p>טוב קוד? יאללה. זאת התוכנית:</p>
<pre><code class="language-scala">import scala.concurrent.{Await, ExecutionContext, Future}
import scala.util.Random
import concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import java.net.{URI, URL}
import java.util.concurrent.{CompletableFuture, Executors}
import scala.language.implicitConversions
import scala.util.chaining._
import scala.collection.parallel.CollectionConverters._
object futures {
private def isPrime(n: Int): Boolean =
2.to(Math.sqrt(n.toDouble).toInt).forall(n % _ != 0)
@main
def virtualThreadsDemo(): Unit =
val s0 = System.nanoTime()
1.to(10000000)
.map(n => Future { isPrime(n) })
.map(Await.result(_, Duration.Inf))
.count(identity)
.pipe(println)
val s1 = System.nanoTime()
1.to(10000000)
.map(isPrime)
.count(identity)
.pipe(println)
val s2 = System.nanoTime()
1.to(10000000)
.par
.map(isPrime)
.count(identity)
.pipe(println)
val s3 = System.nanoTime()
println(s"1 thread = ${s2 - s1}")
println(s"* thread = ${s1 - s0}")
println(s"pmap = ${s3 - s2}")
}
</code></pre>
<p>וכן בשביל המשחק כתבתי אותה גם בפייתון כדי שנוכל להשוות זמנים:</p>
<pre><code class="language-python">import time
import multiprocessing
import math
def isprime(n):
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
if __name__ == "__main__":
pool = multiprocessing.Pool(5)
s0 = time.time_ns()
print(sum(pool.map(isprime, range(10_000_000))))
s1 = time.time_ns()
print(s1 - s0)
</code></pre>
<p>והתוצאות לפחות אצלי על המחשב:</p>
<pre><code class="language-text">1 thread = 2025199791
* thread = 3463589500
pmap = 674844834
python = 14593734000
</code></pre>
<p>לא סיפרתי קודם אבל ניסיתי גם להחליף את ה Executor שמריץ את ה Threads לכזה שמשתמש ב Virtual Threads של Java אבל התוצאות לא עשו חשק לדבר על זה אז קברתי את הניסוי.</p>
<p>מה למדתי? </p>
<ol>
<li><p>חישובים בפייתון עובדים לאט. גם כשניסיתי להריץ את התוכנית בפייתון בלי multiprocessing זה לא עזר.</p></li>
<li><p>אי אפשר סתם ליצור Thread לכל מספר. ככל שהמשימה מסובכת שווה להשקיע זמן ולחשוב איך לחלק אותה למספר תהליכונים.</p></li>
<li><p>לא סתם בחרתי 10 מיליון. במספרים קטנים יותר (אפילו מיליון) כמעט לא היה הבדל בין תהליכון אחד למספר תהליכונים. חשוב להבין טוב את המשימה לפני שבונים פיתרון מבוסס תהליכונים כדי לא לעשות "אופטימיזציות" מיותרות.</p></li>
</ol>
<p>אחת הבעיות של עבודה לא מבוססת Java בתוך ה JVM היא שיש יותר מדי דרכים לעשות דברים, ולא תמיד ברור במה לבחור. במקרה של סקאלה ומקביליות זה נהיה מסובך כי יש גם שיקולים של ארכיטקטורה ותכנות פונקציונאלי.</p>
<p>בשביל המשחק רציתי לספור כמה מספרים ראשוניים יש עד 10 מיליון, ולפצל את החישוב לכמה תהליכונים תוך שימוש בשתי גישות פשוטות למקביליות ובהשוואה עם חישוב סדרתי.</p>
<p>הגישה המקבילית הראשונה היתה פשוט לפתוח Future לכל מספר כדי לזהות אם הוא ראשוני, ולתת ל Java לשגר את התהליכונים.</p>
<p>הגישה המקבילית השניה היתה הספריה parallel-collections שמציעה מימוש של map מקבילי.</p>
<p>וכן נצטרך לעשות פוסט המשך עם cats-effect.</p>
<p>טוב קוד? יאללה. זאת התוכנית:</p>
<pre><code class="language-scala">import scala.concurrent.{Await, ExecutionContext, Future}
import scala.util.Random
import concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import java.net.{URI, URL}
import java.util.concurrent.{CompletableFuture, Executors}
import scala.language.implicitConversions
import scala.util.chaining._
import scala.collection.parallel.CollectionConverters._
object futures {
private def isPrime(n: Int): Boolean =
2.to(Math.sqrt(n.toDouble).toInt).forall(n % _ != 0)
@main
def virtualThreadsDemo(): Unit =
val s0 = System.nanoTime()
1.to(10000000)
.map(n => Future { isPrime(n) })
.map(Await.result(_, Duration.Inf))
.count(identity)
.pipe(println)
val s1 = System.nanoTime()
1.to(10000000)
.map(isPrime)
.count(identity)
.pipe(println)
val s2 = System.nanoTime()
1.to(10000000)
.par
.map(isPrime)
.count(identity)
.pipe(println)
val s3 = System.nanoTime()
println(s"1 thread = ${s2 - s1}")
println(s"* thread = ${s1 - s0}")
println(s"pmap = ${s3 - s2}")
}
</code></pre>
<p>וכן בשביל המשחק כתבתי אותה גם בפייתון כדי שנוכל להשוות זמנים:</p>
<pre><code class="language-python">import time
import multiprocessing
import math
def isprime(n):
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
if __name__ == "__main__":
pool = multiprocessing.Pool(5)
s0 = time.time_ns()
print(sum(pool.map(isprime, range(10_000_000))))
s1 = time.time_ns()
print(s1 - s0)
</code></pre>
<p>והתוצאות לפחות אצלי על המחשב:</p>
<pre><code class="language-text">1 thread = 2025199791
* thread = 3463589500
pmap = 674844834
python = 14593734000
</code></pre>
<p>לא סיפרתי קודם אבל ניסיתי גם להחליף את ה Executor שמריץ את ה Threads לכזה שמשתמש ב Virtual Threads של Java אבל התוצאות לא עשו חשק לדבר על זה אז קברתי את הניסוי.</p>
<p>מה למדתי? </p>
<ol>
<li><p>חישובים בפייתון עובדים לאט. גם כשניסיתי להריץ את התוכנית בפייתון בלי multiprocessing זה לא עזר.</p></li>
<li><p>אי אפשר סתם ליצור Thread לכל מספר. ככל שהמשימה מסובכת שווה להשקיע זמן ולחשוב איך לחלק אותה למספר תהליכונים.</p></li>
<li><p>לא סתם בחרתי 10 מיליון. במספרים קטנים יותר (אפילו מיליון) כמעט לא היה הבדל בין תהליכון אחד למספר תהליכונים. חשוב להבין טוב את המשימה לפני שבונים פיתרון מבוסס תהליכונים כדי לא לעשות "אופטימיזציות" מיותרות.</p></li>
</ol>
tag:www.tocode.co.il,2005:BlogPost/25402024-02-04T13:00:07+02:002024-02-04T13:00:07+02:00המאזניים של AI וקודינון פרקMon, 05 Feb 2024 06:00:00 +0200<p>מצד אחד AI מאפשר לכתוב קוד מהר יותר, ומצד שני AI (היום) לא יודע להציע אבסטרקציות שיובילו לכתיבת פחות קוד.</p>
<p>רק כשנבין את זה נוכל לראות למה מתכנתים מסיימים משימות מהר יותר עם קופיילוט. כבר התרגלנו להסתכל על קוד קיים בתור נטל, ולחלום על פרויקטי Green Fields, והנה מגיע ה AI ומתחיל תמיד מאפס, ומראה לנו שאין שום בעיה להרים מאפס דף נחיתה בלי לקחת את ה CSS-ים של הפרויקט (כי הוא כבר יעצב ויכתוב CSS), או להוסיף עוד שליפה מבסיס הנתונים בלי להשתמש באבסטרקציות שכבר קיימות בקוד, כי הן לא בדיוק מתאימות ל Use Case הנוכחי.</p>
<p>הבעיה שרק להוסיף קוד זו לא אסטרטגיה טובה עבור Senior Developers. זה מעגל שטני שגורם לקוד להיות יותר קשה לתחזוקה לאורך זמן, והקופיילוט בתורו מתעלם מהקושי וממשיך לסבך את המערכת עד שכבר אי אפשר יהיה יותר להיעזר בו.</p>
<p>ההבנה הזאת היא המפתח למעבר מג'וניורים לסניורים.</p>
<p>ג'וניורים מחפשים לפתור בעיות, סניורים מחפשים לבנות אבסטרקציות כדי שיהיה קל יותר לפתור בעיות בעתיד.</p>
<p>מצד אחד AI מאפשר לכתוב קוד מהר יותר, ומצד שני AI (היום) לא יודע להציע אבסטרקציות שיובילו לכתיבת פחות קוד.</p>
<p>רק כשנבין את זה נוכל לראות למה מתכנתים מסיימים משימות מהר יותר עם קופיילוט. כבר התרגלנו להסתכל על קוד קיים בתור נטל, ולחלום על פרויקטי Green Fields, והנה מגיע ה AI ומתחיל תמיד מאפס, ומראה לנו שאין שום בעיה להרים מאפס דף נחיתה בלי לקחת את ה CSS-ים של הפרויקט (כי הוא כבר יעצב ויכתוב CSS), או להוסיף עוד שליפה מבסיס הנתונים בלי להשתמש באבסטרקציות שכבר קיימות בקוד, כי הן לא בדיוק מתאימות ל Use Case הנוכחי.</p>
<p>הבעיה שרק להוסיף קוד זו לא אסטרטגיה טובה עבור Senior Developers. זה מעגל שטני שגורם לקוד להיות יותר קשה לתחזוקה לאורך זמן, והקופיילוט בתורו מתעלם מהקושי וממשיך לסבך את המערכת עד שכבר אי אפשר יהיה יותר להיעזר בו.</p>
<p>ההבנה הזאת היא המפתח למעבר מג'וניורים לסניורים.</p>
<p>ג'וניורים מחפשים לפתור בעיות, סניורים מחפשים לבנות אבסטרקציות כדי שיהיה קל יותר לפתור בעיות בעתיד.</p>
tag:www.tocode.co.il,2005:BlogPost/25392024-02-03T15:00:07+02:002024-02-03T15:00:07+02:00היום למדתי: נקודה בפוליגון ופיתרון AoC 2023 יום 10 חלק 2 בסקאלהינון פרקSun, 04 Feb 2024 06:00:00 +0200<p>לפני שבוע פרסמתי כאן את הפיתרון של החלק הראשון של יום 10 מ Advent Of Code האחרון בסקאלה, בו ראינו איך למצוא מעגל בגרף באמצעות DFS. החלק השני של התרגיל הציג בעיה מעניינת שנקראת <a target="_blank" href="https://en.wikipedia.org/wiki/Point_in_polygon">Point In Polygon</a>. בואו נראה איך זה עובד ואיך לפתור אותה עם אלגוריתם Ray Casting.</p>
<p>לפני שבוע פרסמתי כאן את הפיתרון של החלק הראשון של יום 10 מ Advent Of Code האחרון בסקאלה, בו ראינו איך למצוא מעגל בגרף באמצעות DFS. החלק השני של התרגיל הציג בעיה מעניינת שנקראת <a target="_blank" href="https://en.wikipedia.org/wiki/Point_in_polygon">Point In Polygon</a>. בואו נראה איך זה עובד ואיך לפתור אותה עם אלגוריתם Ray Casting.</p>
tag:www.tocode.co.il,2005:BlogPost/25382024-02-02T15:00:07+02:002024-02-02T15:00:07+02:00שלושה סיפורים קריטיים לקריירה שלנוינון פרקSat, 03 Feb 2024 06:00:00 +0200<p>הסיפור על כסף - כמה אני מרוויח? כמה אתה? כמה מגיע לי? בשביל מה משלמים לי? מה הייתי עושה עם יותר כסף? איך הייתי מסתדר עם פחות? מה הקשר בין כסף למעמד? בין כסף לערך? מה היחס שלי למשפטים כמו "אני צריך כסף כדי לקנות יותר דברים", "אף פעם אין לי מספיק כסף", "מי שמרוויח יותר חשוב יותר", "כסף רק יוצר עוד בעיות", "כסף הוא המפתח שיעזור לפתור בעיות". שילוב כל התשובות בונה סיפור והסיפור הזה קובע חלק משמעותי ממהתנהלות שלנו בעולם.</p>
<p>הסיפור על זמן - חיים רק פעם אחת, החיים קצרים אז צריך להספיק כמה שיותר, בשביל להצליח צריך לעבוד מסביב לשעון, כל היזמים הגדולים ישנו במשרד, מי שמבזבז אפילו שעה אחת לא יודע להעריך את החיים, עמידה בזמנים היא המפתח להצלחה, הדרך הארוכה היא בעצם הקצרה ביותר, לעולם אל תדחה למחר מה שתוכל לעשות היום, תמיד יש זמן לדברים ההכרחיים. המדריך "שבוע עבודה בן 4 שעות" של טים פריס הוא כולו מדריך לסיפור מסוים על זמן.</p>
<p>והסיפור על ידע - במה אני טוב? במה לעולם לא אהיה טוב? איך אני לומד דברים חדשים? מתי אני לומד דברים חדשים? מה זה בכלל ללמוד? מתי אני שוכח מיומנויות ישנות? איזה מיומנויות כדאי לי לשכוח? יש דברים שעדיף ללמוד יחד? יש דברים שאפשר ללמוד רק יחד? </p>
<p>האתגר הראשון הוא לשים לב איך הסיפורים האלה משפיעים על החיים שלנו. אתגר יותר גדול הוא לשנות אותם כשהם כבר לא עוזרים לנו.</p>
<p>הסיפור על כסף - כמה אני מרוויח? כמה אתה? כמה מגיע לי? בשביל מה משלמים לי? מה הייתי עושה עם יותר כסף? איך הייתי מסתדר עם פחות? מה הקשר בין כסף למעמד? בין כסף לערך? מה היחס שלי למשפטים כמו "אני צריך כסף כדי לקנות יותר דברים", "אף פעם אין לי מספיק כסף", "מי שמרוויח יותר חשוב יותר", "כסף רק יוצר עוד בעיות", "כסף הוא המפתח שיעזור לפתור בעיות". שילוב כל התשובות בונה סיפור והסיפור הזה קובע חלק משמעותי ממהתנהלות שלנו בעולם.</p>
<p>הסיפור על זמן - חיים רק פעם אחת, החיים קצרים אז צריך להספיק כמה שיותר, בשביל להצליח צריך לעבוד מסביב לשעון, כל היזמים הגדולים ישנו במשרד, מי שמבזבז אפילו שעה אחת לא יודע להעריך את החיים, עמידה בזמנים היא המפתח להצלחה, הדרך הארוכה היא בעצם הקצרה ביותר, לעולם אל תדחה למחר מה שתוכל לעשות היום, תמיד יש זמן לדברים ההכרחיים. המדריך "שבוע עבודה בן 4 שעות" של טים פריס הוא כולו מדריך לסיפור מסוים על זמן.</p>
<p>והסיפור על ידע - במה אני טוב? במה לעולם לא אהיה טוב? איך אני לומד דברים חדשים? מתי אני לומד דברים חדשים? מה זה בכלל ללמוד? מתי אני שוכח מיומנויות ישנות? איזה מיומנויות כדאי לי לשכוח? יש דברים שעדיף ללמוד יחד? יש דברים שאפשר ללמוד רק יחד? </p>
<p>האתגר הראשון הוא לשים לב איך הסיפורים האלה משפיעים על החיים שלנו. אתגר יותר גדול הוא לשנות אותם כשהם כבר לא עוזרים לנו.</p>
tag:www.tocode.co.il,2005:BlogPost/25372024-02-01T16:00:08+02:002024-02-01T16:00:08+02:00איך לתחזק פרויקט שכמעט לא צריך שינוייםינון פרקFri, 02 Feb 2024 06:00:00 +0200<p>אין ספק שפיתוח מערכת הוא אתגר גדול, אבל כשמפתחים מערכת יום יום ושעה שעה יש גם ייתרון - אפשר להחזיק את הכל (או הרוב) בראש והכלים זמינים. שרת הפיתוח כבר באוויר, שרת הבדיקות רץ, ביצעת כבר התחברות למסך ה admin וכל הספריות בגירסאות החדשות ביותר.</p>
<p>בפרויקט Legacy, בנוסף לאתגר של פיתרון בעיה ספציפית יש עוד המון אתגרים שקשורים לעובדה שאף אחד לא נגע בקוד כמה חודשים או שנים:</p>
<ol>
<li><p>הקוד לא רץ. גם לא מתקמפל.</p></li>
<li><p>אין לי מושג מה הסיסמה למסך הניהול על שרת הבדיקות. היא היתה שמורה לי איפשהו אבל כבר מזמן שכחתי איפה.</p></li>
<li><p>התיעוד ברשת לא רלוונטי כי הפרויקט משתמש בספריות ישנות.</p></li>
<li><p>אין לי מושג מה התפקיד של כל חלק בקוד או איפה למצוא את החלקים שאני צריך לעדכן.</p></li>
</ol>
<p>אפשר לנסות לפתור את רוב הבעיות כאן עם כתיבת קבצי תיעוד ואולי אתם יותר טובים ממני בכתיבת קבצים כאלה, אבל לפחות במקרה שלי אני מצליח לכתוב קבצי תיעוד אבל אף פעם לא זוכר לפתוח אותם כשצריך משהו וככה מצטברים לי המון קבצי תיעוד לא רלוונטיים.</p>
<p>מה כן עובד לי-</p>
<ol>
<li><p>יצירת מנגנון הרצה סטנדרטי, גם לפרויקטים ישנים - כלומר צריך לוודא ש <code dir="ltr">npm start</code> או <code dir="ltr">docker compose up</code> עובדים ומעלים את הסביבה. זה תמיד שווה את ההשקעה.</p></li>
<li><p>כל פעם שאני מגיע לשנות קוד ולא מבין משהו בקוד ישן אני מוסיף תיעוד על אותה פונקציה שמסביר מה היא עושה ולמה. לאט לאט תיעוד נכתב ולאורך שנים יותר קל לי לתקן בעיות.</p></li>
<li><p>לגבי בדיקות - היתרון של בדיקה על תיעוד הוא שאפשר להריץ בדיקה. החיסרון שאם לא הרצת בדיקה חצי שנה יש לה סיכוי טוב להיכשל. אם אתם מצליחים זה מאוד עוזר שיש גיטהאב אקשן שמריץ את הבדיקות פעם בשבוע. חייב להודות שלא בכל הפרויקטים הישנים שלי אני מצליח לשמר את זה.</p></li>
<li><p>בעיה נוספת עם פרויקטים ישנים היא מעקפים קטנים שאנחנו שמים כי חייבים לתקן באג דחוף בפרודקשן ואין באמת זמן לעשות את זה נכון. וככה אנחנו רואים שינויי קוד על מכונת הפרודקשן שלא מגיעים לגיט, במיוחד במערכות ווב שם לא צריך לקמפל את הפרויקט. אומנם לא הצלחתי לגמרי למגר את התופעה הזאת, אבל כן אפשר לנסות כל פעם שרואים דבר כזה פשוט לעשות קומיט לשינוי גם אם אין לך הודעת קומיט מדויקת לכתוב, רק בשביל שהבן אדם הבא לא ייבהל.</p></li>
</ol>
<p>יש לכם עוד טיפים לתחזוקת פרויקטים פעם בכמה חודשים? אל תתביישו לשתף בתגובות.</p>
<p>אין ספק שפיתוח מערכת הוא אתגר גדול, אבל כשמפתחים מערכת יום יום ושעה שעה יש גם ייתרון - אפשר להחזיק את הכל (או הרוב) בראש והכלים זמינים. שרת הפיתוח כבר באוויר, שרת הבדיקות רץ, ביצעת כבר התחברות למסך ה admin וכל הספריות בגירסאות החדשות ביותר.</p>
<p>בפרויקט Legacy, בנוסף לאתגר של פיתרון בעיה ספציפית יש עוד המון אתגרים שקשורים לעובדה שאף אחד לא נגע בקוד כמה חודשים או שנים:</p>
<ol>
<li><p>הקוד לא רץ. גם לא מתקמפל.</p></li>
<li><p>אין לי מושג מה הסיסמה למסך הניהול על שרת הבדיקות. היא היתה שמורה לי איפשהו אבל כבר מזמן שכחתי איפה.</p></li>
<li><p>התיעוד ברשת לא רלוונטי כי הפרויקט משתמש בספריות ישנות.</p></li>
<li><p>אין לי מושג מה התפקיד של כל חלק בקוד או איפה למצוא את החלקים שאני צריך לעדכן.</p></li>
</ol>
<p>אפשר לנסות לפתור את רוב הבעיות כאן עם כתיבת קבצי תיעוד ואולי אתם יותר טובים ממני בכתיבת קבצים כאלה, אבל לפחות במקרה שלי אני מצליח לכתוב קבצי תיעוד אבל אף פעם לא זוכר לפתוח אותם כשצריך משהו וככה מצטברים לי המון קבצי תיעוד לא רלוונטיים.</p>
<p>מה כן עובד לי-</p>
<ol>
<li><p>יצירת מנגנון הרצה סטנדרטי, גם לפרויקטים ישנים - כלומר צריך לוודא ש <code dir="ltr">npm start</code> או <code dir="ltr">docker compose up</code> עובדים ומעלים את הסביבה. זה תמיד שווה את ההשקעה.</p></li>
<li><p>כל פעם שאני מגיע לשנות קוד ולא מבין משהו בקוד ישן אני מוסיף תיעוד על אותה פונקציה שמסביר מה היא עושה ולמה. לאט לאט תיעוד נכתב ולאורך שנים יותר קל לי לתקן בעיות.</p></li>
<li><p>לגבי בדיקות - היתרון של בדיקה על תיעוד הוא שאפשר להריץ בדיקה. החיסרון שאם לא הרצת בדיקה חצי שנה יש לה סיכוי טוב להיכשל. אם אתם מצליחים זה מאוד עוזר שיש גיטהאב אקשן שמריץ את הבדיקות פעם בשבוע. חייב להודות שלא בכל הפרויקטים הישנים שלי אני מצליח לשמר את זה.</p></li>
<li><p>בעיה נוספת עם פרויקטים ישנים היא מעקפים קטנים שאנחנו שמים כי חייבים לתקן באג דחוף בפרודקשן ואין באמת זמן לעשות את זה נכון. וככה אנחנו רואים שינויי קוד על מכונת הפרודקשן שלא מגיעים לגיט, במיוחד במערכות ווב שם לא צריך לקמפל את הפרויקט. אומנם לא הצלחתי לגמרי למגר את התופעה הזאת, אבל כן אפשר לנסות כל פעם שרואים דבר כזה פשוט לעשות קומיט לשינוי גם אם אין לך הודעת קומיט מדויקת לכתוב, רק בשביל שהבן אדם הבא לא ייבהל.</p></li>
</ol>
<p>יש לכם עוד טיפים לתחזוקת פרויקטים פעם בכמה חודשים? אל תתביישו לשתף בתגובות.</p>
tag:www.tocode.co.il,2005:BlogPost/25362024-01-31T18:00:06+02:002024-01-31T18:00:06+02:00בסקאלה זה לא היה קורה (או: מה חדש בטייפסקריפט 5.4)ינון פרקThu, 01 Feb 2024 06:00:00 +0200<p>השינוי הראשון ברשימת <a target="_blank" href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-4-beta/">החידושים של טייפסקריפט 5.4</a> הוא שטייפסקריפט תתחיל לשמור מסקנות על טיפוסים לתוך Closures, או בדוגמה, הקוד הבא כבר יתקמפל בלי שגיאות:</p>
<pre><code class="language-typescript">function getUrls(url: string | URL, names: string[]) {
if (typeof url === "string") {
url = new URL(url);
}
return names.map(name => {
url.searchParams.set("name", name)
// ~~~~~~~~~~~~
// error!
// Property 'searchParams' does not exist on type 'string | URL'.
return url.toString();
});
}
</code></pre>
<p>זו תבנית מוכרת ב JavaScript שמטרתה "לסדר" את המשתנים לפני שממשיכים. בדוגמה שלנו הפונקציה יכולה לקבל מחרוזת או אוביקט URL, כדי שיהיה נוח לאנשים להפעיל אותה, אבל בתוך קוד הפונקציה יותר קל לנו לעבוד עם אוביקטי URL ולכן בתחילת הפונקציה בודקים אם התקבלה מחרוזת ואם כן ממירים ל URL.</p>
<p>גירסאות קודמות של טייפסקריפט לא קישרו את המשתנה url שבתוך הפונקציה הפנימית לשינוי שבוצע בתחילת הפונקציה החיצונית, ולכן בתוך הפונקציה הפנימית היה צריך לבצע את הבדיקה פעם נוספת או לבצע המרה יזומה (או להתעלם מהשגיאה).</p>
<p>החל מגירסה 5.4 הקוד הזה יתחיל לעבוד בלי בעיה, שזה אחלה. אבל השאלה האמיתית היא האם ההמרה מ String ל URL בכלל צריכה להיות חלק מהפונקציה. עכשיו אני יודע TypeScript לא מחפשת להגיד לאנשים מה לעשות והמטרה היא לאפשר בדיקת טיפוסים שתעבוד על קוד שאנשי JavaScript היו שמחים לכתוב. ובכל זאת מעניין לשים לב לתבנית מקבילה בסקאלה לטיפול באותה בעיה. בסקאלה הגישה היא שהמרה מ String ל URL היא "יכולת" של ה String ולא של הפונקציה, או יותר נכון יכולת של String בתוך קובץ או מודול מסוים. בשביל שזה יעבוד אני מגדיר שם פונקציית המרה שנראית כך:</p>
<pre><code class="language-scala"> implicit def stringToURL(url: String): URL =
new URI(url).toURL
</code></pre>
<p>ואם אותה פונקציה נמצאת ב Scope אז אני יכול לכתוב פונקציה שמצפה לקבל URL ולהפעיל אותה עם String והכל פשוט יעבוד:</p>
<pre><code class="language-scala"> def printHost(url: URL): Unit =
println(url.getHost)
@main
def demo(): Unit =
val url: String = "https://www.tocode.co.il"
printHost(url)
</code></pre>
<p>הקומפיילר מפעיל באופן אוטומטי את פונקציית ההמרה ובעצם הפונקציה נקראת עם אוביקט מסוג URL.</p>
<p>היתרון בגישה של סקאלה הוא שאנחנו חוסכים כפל קוד - במקום לכתוב את קוד ההמרה בתחילת כל פונקציה שמקבלת URL מספיק לכתוב פונקציה אחת ולהשאיר אותה ב Scope. גם מבחינת בדיקה של הפונקציה יותר קל להפריד את מנגנון "הכנת" הפרמטרים לפונקציה נפרדת וכך קל לבדוק כל אחת מהפונקציות בנפרד, ובמחינת קריאות גם אם במבט ראשון המנגון של סקאלה נראה מבלבל יש להם כפתור ב IDE שפותח את הסוכר התחבירי הזה ובלחיצת כפתור מראה לנו את ההמרות שמבוצעות בזמן ההפעלה.</p>
<p>השינוי הראשון ברשימת <a target="_blank" href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-4-beta/">החידושים של טייפסקריפט 5.4</a> הוא שטייפסקריפט תתחיל לשמור מסקנות על טיפוסים לתוך Closures, או בדוגמה, הקוד הבא כבר יתקמפל בלי שגיאות:</p>
<pre><code class="language-typescript">function getUrls(url: string | URL, names: string[]) {
if (typeof url === "string") {
url = new URL(url);
}
return names.map(name => {
url.searchParams.set("name", name)
// ~~~~~~~~~~~~
// error!
// Property 'searchParams' does not exist on type 'string | URL'.
return url.toString();
});
}
</code></pre>
<p>זו תבנית מוכרת ב JavaScript שמטרתה "לסדר" את המשתנים לפני שממשיכים. בדוגמה שלנו הפונקציה יכולה לקבל מחרוזת או אוביקט URL, כדי שיהיה נוח לאנשים להפעיל אותה, אבל בתוך קוד הפונקציה יותר קל לנו לעבוד עם אוביקטי URL ולכן בתחילת הפונקציה בודקים אם התקבלה מחרוזת ואם כן ממירים ל URL.</p>
<p>גירסאות קודמות של טייפסקריפט לא קישרו את המשתנה url שבתוך הפונקציה הפנימית לשינוי שבוצע בתחילת הפונקציה החיצונית, ולכן בתוך הפונקציה הפנימית היה צריך לבצע את הבדיקה פעם נוספת או לבצע המרה יזומה (או להתעלם מהשגיאה).</p>
<p>החל מגירסה 5.4 הקוד הזה יתחיל לעבוד בלי בעיה, שזה אחלה. אבל השאלה האמיתית היא האם ההמרה מ String ל URL בכלל צריכה להיות חלק מהפונקציה. עכשיו אני יודע TypeScript לא מחפשת להגיד לאנשים מה לעשות והמטרה היא לאפשר בדיקת טיפוסים שתעבוד על קוד שאנשי JavaScript היו שמחים לכתוב. ובכל זאת מעניין לשים לב לתבנית מקבילה בסקאלה לטיפול באותה בעיה. בסקאלה הגישה היא שהמרה מ String ל URL היא "יכולת" של ה String ולא של הפונקציה, או יותר נכון יכולת של String בתוך קובץ או מודול מסוים. בשביל שזה יעבוד אני מגדיר שם פונקציית המרה שנראית כך:</p>
<pre><code class="language-scala"> implicit def stringToURL(url: String): URL =
new URI(url).toURL
</code></pre>
<p>ואם אותה פונקציה נמצאת ב Scope אז אני יכול לכתוב פונקציה שמצפה לקבל URL ולהפעיל אותה עם String והכל פשוט יעבוד:</p>
<pre><code class="language-scala"> def printHost(url: URL): Unit =
println(url.getHost)
@main
def demo(): Unit =
val url: String = "https://www.tocode.co.il"
printHost(url)
</code></pre>
<p>הקומפיילר מפעיל באופן אוטומטי את פונקציית ההמרה ובעצם הפונקציה נקראת עם אוביקט מסוג URL.</p>
<p>היתרון בגישה של סקאלה הוא שאנחנו חוסכים כפל קוד - במקום לכתוב את קוד ההמרה בתחילת כל פונקציה שמקבלת URL מספיק לכתוב פונקציה אחת ולהשאיר אותה ב Scope. גם מבחינת בדיקה של הפונקציה יותר קל להפריד את מנגנון "הכנת" הפרמטרים לפונקציה נפרדת וכך קל לבדוק כל אחת מהפונקציות בנפרד, ובמחינת קריאות גם אם במבט ראשון המנגון של סקאלה נראה מבלבל יש להם כפתור ב IDE שפותח את הסוכר התחבירי הזה ובלחיצת כפתור מראה לנו את ההמרות שמבוצעות בזמן ההפעלה.</p>
tag:www.tocode.co.il,2005:BlogPost/25352024-01-30T16:00:08+02:002024-01-30T16:00:08+02:00היום למדתי: בדיקת מאפיין ב Beautiful Soup בפייתוןינון פרקWed, 31 Jan 2024 06:00:00 +0200<p>הקוד הבא נכשל כי ללינק השני אין מאפיין href:</p>
<pre><code class="language-python">from bs4 import BeautifulSoup
text = """<div>
<a href="#a">a</a>
<a>not a link</a>
</div>"""
if __name__ == "__main__":
soup = BeautifulSoup(text, features="html.parser")
for link in soup.find_all("a"):
print(link["href"])
</code></pre>
<p>יותר מעניין לשים לב ש bs4.Tag לא מימשו בדיקת שייכות הקוד הזה רץ אבל לא מדפיס אף לינק:</p>
<pre><code class="language-python">for link in soup.find_all("a"):
if "href" in link:
print(link["href"])
</code></pre>
<p>מה עושים? דרך אחת היא להסתכל על מאפיין attrs של התג, שהוא כן מילון:</p>
<pre><code class="language-python">for link in soup.find_all("a"):
if "href" in link.attrs:
print(link["href"])
</code></pre>
<p>דרך שניה היא להשתמש ב get במקום בסוגריים מרובעים, כי get מחזיר None כשהמאפיין לא קיים:</p>
<pre><code class="language-python">for link in soup.find_all("a"):
if link.get("href") is not None:
print(link["href"])
</code></pre>
<p>הקוד הבא נכשל כי ללינק השני אין מאפיין href:</p>
<pre><code class="language-python">from bs4 import BeautifulSoup
text = """<div>
<a href="#a">a</a>
<a>not a link</a>
</div>"""
if __name__ == "__main__":
soup = BeautifulSoup(text, features="html.parser")
for link in soup.find_all("a"):
print(link["href"])
</code></pre>
<p>יותר מעניין לשים לב ש bs4.Tag לא מימשו בדיקת שייכות הקוד הזה רץ אבל לא מדפיס אף לינק:</p>
<pre><code class="language-python">for link in soup.find_all("a"):
if "href" in link:
print(link["href"])
</code></pre>
<p>מה עושים? דרך אחת היא להסתכל על מאפיין attrs של התג, שהוא כן מילון:</p>
<pre><code class="language-python">for link in soup.find_all("a"):
if "href" in link.attrs:
print(link["href"])
</code></pre>
<p>דרך שניה היא להשתמש ב get במקום בסוגריים מרובעים, כי get מחזיר None כשהמאפיין לא קיים:</p>
<pre><code class="language-python">for link in soup.find_all("a"):
if link.get("href") is not None:
print(link["href"])
</code></pre>
tag:www.tocode.co.il,2005:BlogPost/25342024-01-29T17:00:08+02:002024-01-29T17:00:08+02:00שתי דוגמאות שלא אהבתי של Structural Pattern Matching בפייתוןינון פרקTue, 30 Jan 2024 06:00:00 +0200<p>בגדול כתיב ה match / case של פייתון מצוין ויכול לקצר קוד ולאפשר כתיבה יותר מדויקת. אבל יש גם צדדים שליליים ולפעמים משהו שנראה "קריא" או "יפה" יכול רק לבלבל יותר. בואו ננסה לפתוח את זה.</p>
<p>בדוגמה הראשונה מתוך התיעוד יש לנו קטע קוד שמועתק מדג'נגו:</p>
<pre><code class="language-python">if (
isinstance(value, (list, tuple)) and
len(value) > 1 and
isinstance(value[-1], (Promise, str))
):
*value, label = value
value = tuple(value)
else:
label = key.replace('_', ' ').title()
</code></pre>
<p>ואחרי המעבר ל Structural Pattern Matching הוא יראה כך:</p>
<pre><code class="language-python">match value:
case [*v, label := (Promise() | str())] if v:
value = tuple(v)
case _:
label = key.replace('_', ' ').title()
</code></pre>
<p>נכון הקוד השני יותר קצר, אבל הבעיה איתו היא שעלינו להכיר משמעויות חדשות לסימנים מוכרים כדי להבין אותו. הכוכבית מוכרת לנו מפירוק או חיבור מערכים, ופה היא באה לסמן ש v הוא רשימה של כל הערכים מתוך value מלבד האחרון - בסגנון הפקודה:</p>
<pre><code class="language-python">[*v, _] = value
</code></pre>
<p>אבל בפקודה זו חייבים לכתוב שם של משתנה בתור הדבר האחרון (אחרי ה v), בעוד שב match/case אפשר גם להשתמש בערך קבוע.</p>
<p>ה Promise וה str מופיעים שם בתור הפעלה של פונקציות, למרות שבתוכנית אמיתית לא היינו מפעילים פונקציות אלה בשביל לבדוק שייכות למחלקה אלא משתמשים ב <code dir="ltr">isinstance</code> מהדוגמה לפני השינוי.</p>
<p>פעולת match/case הופכת אפילו יותר מסובכת כשמדובר על התאמה למחלקות שלנו. ניקח את הספריה <a href="regex_spm">regex_spm</a> בתור דוגמה ואת הקוד הבא מתוך התיעוד שלה:</p>
<pre><code class="language-python">match regex_spm.fullmatch_in("123,45"):
case r"(\d+),(?P<second>\d+)" as m:
print("Notice the `as m` at the end of the line above")
print(f"The first group is {m[1]}")
print(f"The second group is {m['second']}")
print(f"The full `re.Match` object is available as {m.match}")
</code></pre>
<p>זה נראה כמו קסם עד שאנחנו נזכרים במימוש של הספריה:</p>
<pre><code class="language-python">@dataclass
class RegexSpmMatch:
string: str
_match_func: Callable[[re.Pattern, str], re.Match]
match: re.Match | None = None
def __eq__(self, pattern: str | re.Pattern | tuple[str, int | re.RegexFlag]):
if isinstance(pattern, str):
pattern = re.compile(pattern)
elif isinstance(pattern, tuple):
pattern = re.compile(*pattern)
self.match = self._match_func(pattern, self.string)
return self.match is not None
def __getitem__(
self,
group: int | str | tuple[int, ...] | tuple[str, ...]
) -> str | tuple[str, ...] | None:
return self.match[group]
</code></pre>
<p>בעצם יש לנו dataclass עם מימוש מקוסטם לפונקציה <code dir="ltr">__eq__</code> מה שגורם להתאמה מול ה case. ה as המועתק למשתנה m הוא לא Match Object אלא אותו dataclass מהספריה, שכולל מימוש ל <code dir="ltr">__getitem__</code> כדי לאפשר את כתיב הסוגריים המרובעים.</p>
<p>נשווה את זה לגירסת ה if/else:</p>
<pre><code class="language-python">if m := re.search("(\d+),(?P<second>\d+)", text):
...
elif m := re.search("(\d)-(\d)", text):
...
else:
print("no match")
</code></pre>
<p>וקל לראות שדווקא גישת ה if/else יצאה יותר קריאה ויותר חסכונית. לראיה הקוד הזה שנראה מאוד הגיוני אבל לא עובד עם הספריה:</p>
<pre><code class="language-python">pattern1 = r"my-first-pattern"
pattern2 = re.compile(r"my-second-pattern")
match regex_spm.search_in(my_string):
case pattern1: print("This does not work, it matches any string. Python interprets `pattern1` "
"as simply a new capture variable name, hiding its previous value.")
case pattern2: print("This does not work either")
</code></pre>
<p>סך הכל Structural Pattern Matching זה כן מנגנון ממש אחלה. תשתמשו בו איפה שהגיוני אבל שימו לב להיזהר משימוש יתר.</p>
<p>בגדול כתיב ה match / case של פייתון מצוין ויכול לקצר קוד ולאפשר כתיבה יותר מדויקת. אבל יש גם צדדים שליליים ולפעמים משהו שנראה "קריא" או "יפה" יכול רק לבלבל יותר. בואו ננסה לפתוח את זה.</p>
<p>בדוגמה הראשונה מתוך התיעוד יש לנו קטע קוד שמועתק מדג'נגו:</p>
<pre><code class="language-python">if (
isinstance(value, (list, tuple)) and
len(value) > 1 and
isinstance(value[-1], (Promise, str))
):
*value, label = value
value = tuple(value)
else:
label = key.replace('_', ' ').title()
</code></pre>
<p>ואחרי המעבר ל Structural Pattern Matching הוא יראה כך:</p>
<pre><code class="language-python">match value:
case [*v, label := (Promise() | str())] if v:
value = tuple(v)
case _:
label = key.replace('_', ' ').title()
</code></pre>
<p>נכון הקוד השני יותר קצר, אבל הבעיה איתו היא שעלינו להכיר משמעויות חדשות לסימנים מוכרים כדי להבין אותו. הכוכבית מוכרת לנו מפירוק או חיבור מערכים, ופה היא באה לסמן ש v הוא רשימה של כל הערכים מתוך value מלבד האחרון - בסגנון הפקודה:</p>
<pre><code class="language-python">[*v, _] = value
</code></pre>
<p>אבל בפקודה זו חייבים לכתוב שם של משתנה בתור הדבר האחרון (אחרי ה v), בעוד שב match/case אפשר גם להשתמש בערך קבוע.</p>
<p>ה Promise וה str מופיעים שם בתור הפעלה של פונקציות, למרות שבתוכנית אמיתית לא היינו מפעילים פונקציות אלה בשביל לבדוק שייכות למחלקה אלא משתמשים ב <code dir="ltr">isinstance</code> מהדוגמה לפני השינוי.</p>
<p>פעולת match/case הופכת אפילו יותר מסובכת כשמדובר על התאמה למחלקות שלנו. ניקח את הספריה <a href="regex_spm">regex_spm</a> בתור דוגמה ואת הקוד הבא מתוך התיעוד שלה:</p>
<pre><code class="language-python">match regex_spm.fullmatch_in("123,45"):
case r"(\d+),(?P<second>\d+)" as m:
print("Notice the `as m` at the end of the line above")
print(f"The first group is {m[1]}")
print(f"The second group is {m['second']}")
print(f"The full `re.Match` object is available as {m.match}")
</code></pre>
<p>זה נראה כמו קסם עד שאנחנו נזכרים במימוש של הספריה:</p>
<pre><code class="language-python">@dataclass
class RegexSpmMatch:
string: str
_match_func: Callable[[re.Pattern, str], re.Match]
match: re.Match | None = None
def __eq__(self, pattern: str | re.Pattern | tuple[str, int | re.RegexFlag]):
if isinstance(pattern, str):
pattern = re.compile(pattern)
elif isinstance(pattern, tuple):
pattern = re.compile(*pattern)
self.match = self._match_func(pattern, self.string)
return self.match is not None
def __getitem__(
self,
group: int | str | tuple[int, ...] | tuple[str, ...]
) -> str | tuple[str, ...] | None:
return self.match[group]
</code></pre>
<p>בעצם יש לנו dataclass עם מימוש מקוסטם לפונקציה <code dir="ltr">__eq__</code> מה שגורם להתאמה מול ה case. ה as המועתק למשתנה m הוא לא Match Object אלא אותו dataclass מהספריה, שכולל מימוש ל <code dir="ltr">__getitem__</code> כדי לאפשר את כתיב הסוגריים המרובעים.</p>
<p>נשווה את זה לגירסת ה if/else:</p>
<pre><code class="language-python">if m := re.search("(\d+),(?P<second>\d+)", text):
...
elif m := re.search("(\d)-(\d)", text):
...
else:
print("no match")
</code></pre>
<p>וקל לראות שדווקא גישת ה if/else יצאה יותר קריאה ויותר חסכונית. לראיה הקוד הזה שנראה מאוד הגיוני אבל לא עובד עם הספריה:</p>
<pre><code class="language-python">pattern1 = r"my-first-pattern"
pattern2 = re.compile(r"my-second-pattern")
match regex_spm.search_in(my_string):
case pattern1: print("This does not work, it matches any string. Python interprets `pattern1` "
"as simply a new capture variable name, hiding its previous value.")
case pattern2: print("This does not work either")
</code></pre>
<p>סך הכל Structural Pattern Matching זה כן מנגנון ממש אחלה. תשתמשו בו איפה שהגיוני אבל שימו לב להיזהר משימוש יתר.</p>