fa
Feedback
ToCode

ToCode

رفتن به کانال در Telegram

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

نمایش بیشتر
1 420
مشترکین
+124 ساعت
+17 روز
-430 روز
آرشیو پست ها
ToCode
1 420
פיתרון Advent Of Code 2023 יום 9 בסקאלה כשלומדים רקורסיה לא תמיד ברור למה צריך את המבנה הזה, הרבה פעמים בגלל שאנחנו מנסים לפתור בצורה רקורסיבית שאלות שלא הכי מתאימות לחשיבה הרקורסיבית. המקרה של יום 9 ב Advent Of Code השנה המחיש איך שאלות מסוימות מזמינות את הפיתרון הרקורסיבי, ונפתרות בקלות בזכותו. התרגיל המשימה שלנו היא לחשב את האיבר הבא בסידרה דרך חישוב סדרת ההפרשים שלה. ניקח לדוגמה את סידרת המספרים:
1   3   6  10  15  21
אז אפשר לרשום את ההפרש בין כל שני מספרים ולקבל את הסידרה:
2   3   4   5   6
ואפשר להמשיך ולרשום את ההפרש בין כל מספר בסידרת ההפרשים כדי לקבל את הסידרה:
1   1   1   1
ואז להמשיך עוד שלב כדי לקבל:
0   0   0
כשמגיעים לסידרת האפסים נוסיף אפס אחד, ואז נשתמש בו כדי לחשב את האיבר הבא בסידרת ה-1-ים (הוא יהיה 1), ואז נשתמש בו כדי לגלות את האיבר הבא בסידרה השנייה (הוא יהיה 7) ובעזרתו מוצאים את המספר האחרון בסידרה שממנה התחלנו - הוא יהיה 28. פיתרון בסקאלה נכתוב פונקציה בסקאלה שתחשב את המספר הבא בסידרה. לפונקציה יש בסך הכל שתי אפשרויות: 1. או שהיא מקבלת סידרה שכולה אפסים, ואז היא מוסיפה לה עוד 0. 2. או שהיא מקבלת סידרה אחרת, ואז היא תחבר את האיבר האחרון בסידרה עם האיבר האחרון בסידרה שתתקבל מהפעלת אותה פונקציה על סידרת ההפרשים. את סידרת ההפרשים מחשבים בסקאלה עם השורה הבאה:
val diffSeries = known.zip(known.tail).map {(i, j) => j - i }
ולכן הפונקציה כולה תהיה:
  def addNextValue(known: List[Long]): List[Long] =
    known match
      case _ if known.forall(l => l == 0) => known :+ 0
      case _ =>
        val diffSeries = known.zip(known.tail).map {(i, j) => j - i }
        known :+ known.last + addNextValue(diffSeries).last

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

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

ToCode
1 420
שימוש דוגמטי, שכל ישר ומה עושים עם הגו'ניורים? חברות פיתוח רציניות היום מפעילות תהליך של Code Review על קוד חדש שנכנס למערכת, והרבה פעמים צוותים ינסו לתחזק תהליך כזה גם כשאין מספיק כח אדם כדי לעבור אחד על הקוד של השני. בשטח זה מוביל לשתי תוצאות גרועות: 1. הראשונה היא שימוש דוגמטי ב Best Practices, כי אם אין לך זמן לעבור על קוד ובכל זאת לא נעים לא להגיד כלום, אז אתה תעיר על הדברים הקטנים שקופצים לעין (למה אין שתי שורות רווח לפני הפונקציה? הקומפוננטה הזאת עושה יותר מדי וכו). 2. השניה היא איטיות בפיתוח, כי במקום להכניס קוד ולראות מה נשבר אנחנו צריכים לחכות להערות לא רלוונטיות של ראש הצוות העסוק מדי. לאורך זמן המטרה של Code Reviews היא לשפר את איכות הקוד של המערכת, ולעזור לג'וניורים להשתפר ולהיחשף לתבניות פיתוח טובות יותר. הנה שלוש שאלות ששווה לשאול את עצמנו לגבי תהליכי ה Code Review (גם הוותיקים וגם הצעירים)- 1. באיזו תדירות אני מעביר או מקבל הערות ששינו את ה Design של הפיצ'ר בצורה משמעותית? 2. באיזו תדירות אני מעביר או מקבל הערות הקשורות לאבטחת מידע או שתפסו בעיית ביצועים משמעותית לפני שהגיעה לפרודקשן? 3. באיזו תדירות אני מעביר או מקבל הערות שגרמו לי ללמוד פרדיגמה או רעיון חדש, או שגרמו לי לראות אחרת את המערכת ואת תהליך הפיתוח? אם תהליך ה Code Review שלכם לא עובד טוב כמו שהייתם רוצים אל תתביישו לשנות אותו. מעבר לעומק על PR אחד בחודש נותן יותר ערך ממעבר חטוף על שלושה PR-ים כל יום.

ToCode
1 420
נתחיל בדרך שמובנית בשפה אחת הטעויות הנפוצות בלימוד טכנולוגיה מתחילה כשאנחנו מסתכלים על Tech Stack במקום על שפה, ומנסים ללמוד סטאק שלם במכה אחת. זה יהיה הניסיון ללמוד Node.js יחד עם אקספרס, או רובי יחד עם ריילס, אבל גם ריאקט יחד עם רידאקס ו next ו styled components ו TypeScript. לכאורה יש פה הזדמנות להרוויח - במקום ללמוד קודם X אחר כך Y אחר כך Z אני אלמד ישר את הדרך הנכונה לעשות דברים, ואחרי זה אם יעניין אותי להתקדם אוכל להתעמק בכל טכנולוגיה. בפועל המשחק הזה פועל לרעתנו מכמה סיבות: 1. אנחנו מפתחים תלות בפריימוורק כך שיהיה לנו קשה להחליף חלקים ממנה. 2. אנחנו מתרגלים לדרך חשיבה מסוימת, בלי להבין את הפוטנציאל האמיתי של הכלי. 3. ואולי הכי גרוע, פריימוורקים מסתירים מאיתנו את הפרטים של "איך דברים עובדים" ומגדילים את הסיכוי שנכתוב קוד לא הגיוני. לפני שרצים ללמוד את כל הסטאק נסו לראות שאתם מצליחים להסתדר רק עם הטכנולוגיה אותה אתם מנסים ללמוד. כן אפשר להסתדר ממש טוב עם ריאקט גם בלי Redux ו next ולימוד הדברים לפי הסדר רק יעזור להבין את הקשרים ביניהם.

ToCode
1 420
האם Exceptions צריכים להיות חלק מהממשק? ג'אווה ניסו לטעון ש Exceptions הם חלק מהממשק של הפונקציה, רק בשביל לגלות שמתכנתים משתמשים יותר מדי ב Runtime Exceptions כשאין להם כח לתעד את הדברים הרעים שיכולים לקרות. ראסט מנסה את אותו משחק עם החזרה מוקדמת של ערכי שגיאה וסימני שאלה, אבל עדיין מוקדם להבין איך זה ייגמר. בצד השני של המפה יש לנו את פייתון שמציינת ב PEP484: > No syntax for listing explicitly raised exceptions is proposed. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring. וגם בטייפסקריפט היה דיון אינסופי שנגמר בלי המלצה משמעותית. הבעיה הגדולה שלנו היא שמצד אחד אנחנו לא רוצים להכריח אף אחד לטפל בשגיאות (כי לכל פרויקט יש דרישות שונות), אבל מצד שני זה מרגיש לא נכון לטעון שיש לנו Type Safety כשבעצם קוד יכול "לקפוץ" למקומות אחרים בגלל פקודה בפונקציה פנימית. מה דעתכם? האם Exceptions צריכים להיות חלק מהממשק? מעדיפים קוד שמחזיר שגיאה או שזורק Exception, ובאיזה מצבים?

ToCode
1 420
האם Exceptions צריכים להיות חלק מהממשק? ג'אווה ניסו לטעון ש Exceptions הם חלק מהממשק של הפונקציה, רק בשביל לגלות שמתכנתים משתמשים יותר מדי ב Runtime Exceptions כשאין להם כח לתעד את הדברים הרעים שיכולים לקרות. ראסט מנסה את אותו משחק עם החזרה מוקדמת של ערכי שגיאה וסימני שאלה, אבל עדיין מוקדם להבין איך זה ייגמר. בצד השני של המפה יש לנו את פייתון שמציינת ב PEP484: > No syntax for listing explicitly raised exceptions is proposed. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring. וגם בטייפסקריפט היה דיון אינסופי שנגמר בלי המלצה משמעותית. הבעיה הגדולה שלנו היא שמצד אחד אנחנו לא רוצים להכריח אף אחד לטפל בשגיאות (כי לכל פרויקט יש דרישות שונות), אבל מצד שני זה מרגיש לא נכון לטעון שיש לנו Type Safety כשבעצם קוד יכול "לקפוץ" למקומות אחרים בגלל פקודה בפונקציה פנימית. מה דעתכם? האם Exceptions צריכים להיות חלק מהממשק? מעדיפים קוד שמחזיר שגיאה או שזורק Exception, ובאיזה מצבים?

ToCode
1 420
מה אנחנו מחפשים בשפת תכנות מישהו ברדיט של סקאלה הפיל פצצה השבוע ושאל "למה בעצם הפסקתם להשתמש בסקאלה?". זה מעניין כי סקאלה באמת נמצאת בדעיכה בערך מ 2017. שלוש הסיבות המרכזיות שעלו שם הן: 1. קשה לגייס אנשים ועלות מפתחים מאוד גבוהה. 2. אקוסיסטם מפוצל ולא מתוחזק. 3. אין עבודה. בהיבט של האקוסיסטם לסקאלה היה את spark ואת play, שניהם עדיין פעילים ומושכים מתכנתים אבל הרבה פחות מבעבר. ספארק עובד טוב עם פייתון ובשביל לפתח אפליקציות ווב אנחנו מעדיפים את טייפסקריפט. בסוף אנחנו רוצים לבחור שפה שיכולה לתת תשובה טובה לשאלה "למה השפה הזאת טובה". זה יכול להיות ביצועים, מוצר דגל או תחום מסוים בו השפה שולטת. פייתון השתלטה על תחום ניתוח נתונים ו AI, ומשם הפכה לפייבוריטית של הרבה אנשים. ג'אווהסקריפט הרוויחה כי היא היתה היחידה שרצה בתוך דפדפן בצורה מובנית. רובי הרוויחה כשריילס היה מעניין ו swift איתנו בגלל האייפון. למרות המעמד הגבוה של Python ו JavaScript בימים אלה, כדאי לזכור את הלקח של סקאלה, רובי, פרל ושפות רבות נוספות. העולם ישתנה ושפות חדשות יגיעו. ככל שאפשר שווה להתרגל ללימוד שפות תכנות חדשות אפילו בתור תחביב, כדי שכשיגיע היום והשפה שלנו תתחיל לאבד גובה נוכל להתקדם ולא להרגיש תקועים.

ToCode
1 420
פיתרון יום 6 ב Advent Of Code 2023 בסקאלה (הכי קל בינתיים) אחרי 5 ימים מבלבלים, יום 6 של Advent Of Code השאיר קצת אוויר לנשימה. בואו נצא לדרך למירוץ הסירות עם סקאלה. האתגר במשחק היום אנחנו מקבלים טבלה של זמנים ומרחקים:
Time:      7  15   30
Distance:  9  40  200
כל עמודה מתארת שיא במירוץ והמשימה שלנו היא לבחור מהירות שתביס את אותו שיא. הטריק הוא שבשביל להגיע למהירות מסוימת צריך "לטעון" את הסירה וזה מבזבז זמן, וכך המסלול מחולק ל-2, אנחנו בוחרים כמה שניות "לטעון" ואז יוצאים לדרך במהירות ששווה למספר השניות שהשקענו בטעינה. לדוגמה אם השקענו 2 מילי שניות בטעינה הסירה תיסע במהירות 2 מילימטר במילי שניה. בסירה הזאת יש לנו כמה אפשרויות להביס את השיא של המירוץ הראשון: 1. אם נטען במשך 2 מילי שניות יהיו לנו עוד 5 מילי שניות לנסוע במהירות 2, ונעבור מרחק של 10 מילימטר (גדול מהמרחק בטבלה). 2. אם נטען במשך 3 מילי שניות יהיו לנו עוד 4 מילי שניות לנסוע ונעבור 12 מילימטר. 3. אם נטען במשך 4 מילי שניות יהיו לנו עוד 3 מילי שניות לנסוע ונעבור שוב 12 מילימטר. 4. אם נטען במשך 5 מילי שניות יהיו לנו עוד 2 מילי שניות לנסוע ונעבור 10 מילימטר. במירוץ השני יש 8 דרכים לנצח ובשלישי 9 דרכים לנצח, והמשימה היא לכפול את מספר הדרכים לנצח בכל מירוץ. בדוגמה מקבלים 288. פיתרון בעזרת חישוב הסיבה שיום 6 היה כל כך קל היא שאפשר לחשב בקלות את מספר הדרכים לנצח בכל מירוץ. אם נסמן את זמן הטעינה ב x ואת זמן המירוץ ב t אז המרחק שנעבור יהיה:
(t-x) * x
ואז כל מה שצריך זה למצוא מתי המרחק הזה גדול יותר מהמרחק שבטבלה. אם נסמן את המרחק שבטבלה ב d יש לנו משוואה:
(t-x) * x > d
ונשאר לנו רק לפתור את המשוואה ולמצוא את כל המספרים השלמים בטווח בין שני הפיתרונות. קוד? ברור:
import Math.{sqrt, pow, max, min}
import scala.io.Source
import scala.util.chaining.*

object aoc2023day6 {
  val demoInput: String = """Time:      7  15   30
                            |Distance:  9  40  200""".stripMargin

  private def parseInput(input: Source): List[(Long, Long)] =
    input.getLines().toList match
      case List(times, distances) =>
        val timesValues = """\b(\d+)\b""".r.findAllIn(times).toList.map(_.toLong)
        val distanceValues = """\b(\d+)\b""".r.findAllIn(distances).toList.map(_.toLong)
        timesValues.zip(distanceValues)

  private def waysToWin(time: Long, distance: Long): Long =
    val x1 = ((time + sqrt(pow(time, 2) - 4 * distance)) / 2)
    val x2 = ((time - sqrt(pow(time, 2) - 4 * distance)) / 2)
    val start = min(x1, x2).floor + 1
    val end = max(x1, x2).ceil - 1

    (end - start + 1).toLong

  @main
  def day6_part1(): Unit =
    println(parseInput(Source.fromResource("day6.txt"))
      .map(waysToWin)
      .product)
}

אולי הפלוס היחיד של סקאלה כאן היא הפונקציה product שמחזירה את מכפלת כל האיברים ברשימה. חלק 2 בחלק השני של התרגיל אנחנו צריכים לדמיין שיש רק מרוץ אחד - פשוט מוחקים את כל הרווחים בטבלה ומקבלים עמודה אחת, ואז צריך לחשב כמה דרכים יש לנצח באותו מירוץ יחיד. האמת שמבחינת קוד מעט מאוד משתנה, אולי רק פיענוח הקלט. זאת הפונקציה שמפענחת את הקלט לחלק השני:
  private def parseInputPart2(input: Source): (Long, Long) =
    input.getLines().toList match
      case List(times, distances) =>
        val timesValues = times.replaceAll("\\s", "").substring(5).toLong
        val distanceValues = distances.replaceAll("\\s", "").substring(9).toLong
        (timesValues, distanceValues)
השימוש ב substring מוריד את הטקסט בתחילת השורה (המילים Time ו Distance). וכן אפשר היה לבנות שם ביטוי רגולארי יותר גנרי אבל בהקשר של התרגיל לא היה הרבה טעם.

ToCode - آمار و تحلیل کانال تلگرام @tocodeil