פיתרון פרויקט אוילר שאלה 12 בסקאלה
פרויקט אוילר מציע אוסף אינסופי של תרגילים קטנים ואפשר להיעזר בהם כדי ללמוד שפות תכנות חדשות. מאחר ואני לומד סקאלה חשבתי לנסות לפתור איתה את בעיה 12, כדי ללמוד איך להשתמש ברשימות עצלות.
טיפים קצרים וחדשות למתכנתים
פרויקט אוילר מציע אוסף אינסופי של תרגילים קטנים ואפשר להיעזר בהם כדי ללמוד שפות תכנות חדשות. מאחר ואני לומד סקאלה חשבתי לנסות לפתור איתה את בעיה 12, כדי ללמוד איך להשתמש ברשימות עצלות.
באחת השאלות הראשונות בפרויקט אוילר עלינו למצוא את המספר הפלינדרומי הגדול ביותר שהוא מכפלה של שני מספרים תלת ספרתיים. פיתרון שאני רואה הרבה מתלמידים שרק מתחילים לכתוב פייתון יהיה משהו כזה:
max_found = 0
for i in range(100, 1_000):
for j in range(100, 1_000):
product = i * j
if str(product) == str(product)[::-1] and product > max_found:
max_found = product
print(max_found)
אבל למרות שהקוד פשוט ועובד, אם נחזור אליו בעוד כמה שבועות עלול לקחת לנו זמן להבין מה בדיוק קורה בו. הסיבה לבעיית הקריאות היא שיש בקוד מספר מנגנונים המשולבים אחד בתוך השני:
איתור כל המספרים שהם מכפלה של שני מספרים תלת ספרתיים.
איתור כל המספרים מתוכם שהם פלינדרומים.
חישוב המספר המקסימלי.
הדברים משולבים יחד כך שלדוגמה חישוב המספר המקסימלי דורש קוד גם לפני הלולאה וגם בתוך הלולאה הפנימית. אי אפשר להפריד כל מנגנון ואי אפשר להרכיב ולפרק אותם.
ארגון מחדש של הקוד בחשיבה על המנגנונים והממשק ביניהם נראה כך:
import itertools
three_digit_numbers = range(10, 1_000)
pairs = itertools.product(three_digit_numbers, repeat=2)
products = (i * j for (i, j) in pairs)
palindroms = (p for p in products if str(p) == str(p)[::-1])
print(max(palindroms))
הפעם לכל שורה יש תפקיד והקשר בין תפקיד כל שורה למשימה הגדולה ברור גם מהקוד וגם משמות המשתנים. בניית קוד כמו לגו, כמו בלוקים שאפשר לחבר ולנתק, היא המפתח לקוד קריא שיוכל לשרוד לאורך זמן.
דרך אחת לייצר פחות באגים היא לבנות מנגנונים נוגדי באגים - לדוגמה בדיקות אוטומטיות, שימוש ב Strong Types, שילוב בודקי תוכנה בתהליך הפיתוח ותוספת הכשרות לצוות.
דרך אחרת היא לזהות ולהעניש אנשים שמייצרים באגים. ו"עונש" כאן יכול להיות Shaming פומבי, אבל גם יכול להיות לאלץ אותם לעבוד שעות נוספות כדי לתקן את הבאגים (מה שבטעות נקרא Code Ownership).
הבעיה שמה שגורם לאנשים לתמוך בדרך מסוימת לא מבוסס על מדד אוביקטיבי. מי שחושבת שהיא "טובה מדי בשביל להגדיר טיפוסים" או "כותבת בלי באגים" לא תשתכנע מאיזה סיפור על חברה שהצליחה להוריד את כמות הבאגים בעזרת מעבר ל TypeScript, כי היא בטוחה שהבעיה היא באנשים ועדיף היה לגייס מתכנתים "טובים יותר". וגם להיפך, מי שבטוחה שתרבות עבודה ומנגנונים הם הדרך לכתוב קוד טוב יותר לא תשתכנע מאיזה סיפור על חברה שהצליחה לכתוב קוד יותר טוב כשהפסיקו לכתוב בדיקות, כי היא תסביר לעצמה שאולי לאנשים שעובדים שם זה התאים אבל אצלה בצוות זה לא יעבוד.
ואם אי אפשר לשכנע נשאר לנו רק לבחור. להבין איזה דרך מתאימה יותר עבורי, איך אני רוצה לעבוד, ולחפש פרויקט שזו תרבות העבודה בו. "תרבות העבודה כאן לא מתאימה לי" זו אחת הסיבות המוצדקות לחפש עבודה חדשה.
מבחני בית הם רע הכרחי בעולם שלנו. כן יש חלופות אבל גם הן לא תמיד אפשריות או טובות - לדוגמה שאלות תכנות על הלוח בזמן הראיון יכולות להלחיץ, ולא תמיד למועמד או למועמדת יש קוד ציבורי שיכולים לשתף.
הבעיה עם מבחני בית היא שמועמדים לא יודעים מה אתם רוצים לראות, ויכול להיות שהם יכתבו קוד שנראה לכם "נוראי" אפילו שהם לגמרי מסוגלים לכתוב קוד "מושלם". מאחר והמטרה שלנו בגיוס היא לזהות מועמדים טובים, הנה כמה טיפים שיוכלו לעזור למועמדים שלכם ולכם לתקשר טוב יותר.
אופרטור שלוש נקודות ברובי קיים מסתבר כבר די הרבה זמן (מגירסה 2.7), אבל איכשהו עבר לי מתחת לרדאר. תפקידו להעביר את כל הפרמטרים שפונקציה מקבלת לפונקציה אחרת, כלומר לגרום לקוד כזה לעבוד:
def bar(x, y, z, **extra)
puts "x = #{x}, y = #{y}, z = #{z}, #{extra}"
end
def foo(...)
bar(...)
end
foo(10, 20, 30, hello: "world")
האופרטור מחליף תבנית ישנה יותר וקצת יותר מסורבלת שגם קיימת בפייתון:
def bar(x, y, z, **extra)
puts "x = #{x}, y = #{y}, z = #{z}, #{extra}"
end
def foo(*args, **kwargs)
bar(*args, **kwargs)
end
foo(10, 20, 30, hello: "world")
ודבר נוסף - בעבודה על פרויקט קיים, במיוחד כשלא משדרגים את השפה כשיוצאת גירסה חדשה, קל לפספס תחביר חדש ואפילו להעביר שנים בלי לשים לב אליו. זה לא קורה לי בשפות תכנות שאני מלמד כי שם הצרכים שונים. מאחר וגירסה חדשה של שפת תכנות יוצאת מקסימום פעם בשנה, שווה לעשות הרגל ולבדוק את רשימת החידושים בכל גירסה כשהיא יוצאת. גם אם נבחר לא להשתמש בכל טריק חדש שמתווסף, זה לא נעים להיות מופתעים באיחור של 4 שנים.
דרך אחת להסתכל על Code Review היא בתור ה"לקוח" או ה"מנהל" שצריך לוודא את איכות העבודה ועמידה בסטנדרטים. בגישה זו המבקר הוא צוואר בקבוק, הוא חייב לוודא כל שורה בקוד כמו שאנחנו בודקים רכב יד שניה לפני קנייה. קבלת PR היא חותמת איכות, ורק קוד שעובר את תהליך הבקרה רשאי להיכנס למערכת.
דרך שניה להסתכל על Code Reviews היא בתור הזדמנות לחזק את חברי הצוות. בגישה זו המבקר מקבל הצצה לתהליך המחשבה של המתכנתת שכתבה את הקוד ויכול לשאול - "למה התכוונת פה?", "מה אם הקובץ לא קיים?" או "שמתי לב לאופטימיזציה שאפשר להוסיף כאן". המבקר בגישה זו אינו צוואר בקבוק. אפשר לקבל את העצות, אפשר להשאיר אותן בצד, ואפשר לדחות אותן לגמרי.
אנחנו הרבה פעמים חושבים שהגישה הראשונה היא טובה יותר למערכת, כיוון שהיא שומרת על רמה גבוהה של קוד. אבל לאורך זמן הגישה השניה מנצחת, כי היא שומרת על רמה גבוהה יותר של האנשים (גם אלה שכותבים קוד, וגם אלה שמבקרים אותו).
יש משהו חמוד כשספריית תוכנה מתאמצת כדי שדברים יעבדו בצורה אינטואיטיבית אבל יש בזה גם סכנה - והיא שהצעד האינטואיטיבי הבא כבר לא יעבוד. זה הסיפור עם ספריית circe של סקאלה, ספריה לעבודה עם JSON-ים.
אין דבר יותר מייאש מלצפות במדריך וידאו בלי להבין כלום ולהרגיש "אני לא חכם מספיק בשביל הקורס הזה" (אולי רק להמשיך לדף התרגילים שייתן את המכה הסופית). אבל זה קורה לכם וזה קורה לי וזה קורה לכולם, וכשזה קורה בשביל לא להיכנס לייאוש אני מנסה להחליף את המשפט במשהו פחות מייאש אבל שעדיין אפשר לקבל אותו, למשל:
וואו יש פה המון דברים חדשים שאני צריך ללמוד.
אני רואה שאצטרך הרבה יותר זמן ממה שתכננתי בשביל לסיים את הקורס הזה.
אולי זה עדיין מוקדם מדי בשבילי להיכנס לחומר הזה.
המטרה היא להחליף את הייאוש במוטיבציה ללמוד משהו אחר, בדגש על משהו יותר בסיסי מהדבר המסובך. ברור שיש דברים שאני לא חכם מספיק בשביל להבין, אבל רוב הסיכויים שאם אגיע יותר מוכן אצליח להבין על מה מדברים שם.
המשפט שאני הכי אוהב להגיד, והכי לא אוהב לשמוע.
לא היית רוצה לשמוע "בוא ננסה" מהרופאה לפני שאתה נכנס לניתוח.
לא היית רוצה לשמוע "אולי זה יעבוד" מהאינסטלטור כשיש סתימה בצנרת.
אבל כמתכנתים "בוא ננסה" היא האופציה היחידה קדימה. כשהטכנולוגיה מתקדמת יותר מהר מהתיעוד, "בוא ננסה" הוא הסימן שאנחנו פותרים בעיות חדשות, שאנחנו עדיין לא יודעים מה יעבוד, ושאנחנו מוכנים לקחת את הסיכון.
בוא ננסה. אולי זה יעבוד.
יש דברים בתכנות שהם באמת קשים או אפילו בלתי אפשריים. רוב הזמן אנחנו יודעים לזהות אותם ולהימנע מהם, או אם מחליטים להתמודד עם אתגר כזה להקצות לו את המשאבים הנדרשים (וגם להיות ערוכים לכישלון).
אבל רוב הבעיות "הקשות" הן בכלל לא כאלה. כלומר הקושי הוא מלאכותי בגלל סט כלים או אילוצים מסוים שכבר בחרנו. זה הצורך להשתמש בקומפוננטה מסוימת שכתובה לאנגולר, אבל המערכת שלך בנויה בריאקט. או הרצון לשלב ספריית קוד שלא מסתדרת בגירסאות עם ספריה אחרת שכבר נמצאת ביישום. או שאילתה לבסיס נתונים שעובדת ממש בסדר בפיתוח אבל על מידע אמיתי בפרודקשן לוקחת יותר מדי זמן.
ובמצבים האלה שאני רק רוצה לצעוק "אבל זה עובד!" ולפתוח פרויקט Green Field מאפס שבו הכל יסתדר, אני אוהב להזכיר לעצמי שחלק מהמשחק זה בדיוק לעבוד בתוך מערכת האילוצים. לזהות את הפסיק הקטן שלא מסתדר עם המערכת הקיימת ולהצליח לשלב את הדברים בלי לשבור.