fa
Feedback
ToCode

ToCode

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

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

نمایش بیشتر
1 419
مشترکین
اطلاعاتی وجود ندارد24 ساعت
اطلاعاتی وجود ندارد7 روز
اطلاعاتی وجود ندارد30 روز
آرشیو پست ها
ToCode
1 419
# הנתיב השמח זה לא סוד שכבר מעל עשר שנים חברות מצמצמות באנשי ה QA שלהן. שינוי זה הוא תוצאה של המון תהליכים ושינויי תרבות בעולם הפיתוח - בעיקר המעבר להסתמך על בדיקות אוטומטיות, גישות פיתוח TDD בהן מתכנתים כותבים בדיקות לקוד של עצמם ו CI/CD שמוביל לרצון כל הזמן לדחוף קוד חדש לפרודקשן כמה שיותר מהר. ואחת הבעיות עם מתכנתים שכותבים את הבדיקות לקוד של עצמם היא הנתיב השמח: מתכנתים תמיד יחפשו לבדוק שהדברים הנכונים עובדים ושהדברים הלא נכונים לא עובדים. אנחנו יודעים לכתוב בדיקות לקוד שכתבנו כי אנחנו כתבנו אותו, ובודקים את כל הדברים שחשבנו עליהם בעת כתיבת הקוד. אבל - אנחנו לא כל כך טובים בלבדוק את הדברים שלא חשבנו עליהם בקוד (כי, נו, אנחנו עדיין לא חושבים עליהם כשכותבים את הבדיקות). נזכרתי בנתיב השמח כשקראתי על הפירצה האחרונה באתר הביטוח הלאומי: שם היה שדה בטופס של תאריך הנפקת תעודת זהות, אבל אף אחד לא בדק שתאריך ההנפקה שמשתמש הקליד באמת מתאים לתעודת הזהות שהוא הכניס, ובעצם היה אפשר להיכנס עם כל תעודת זהות גם בלי לדעת מה היה תאריך ההנפקה. אני לא מכיר איש QA שהיה מפספס את זה. אני כן מכיר המון מתכנתים שכותבים לעצמם את הבדיקות שיכתבו בקלות בדיקות אוטומטיות לנתיב השמח וישכחו לבדוק מצב של תעודת זהות ותאריך הנפקה לא תואמים. יש המון צדדים טובים לתרבות הסטארט-אפים וטוב שביטוח לאומי וגופים ממשלתיים אחרים לומדים איך לרוץ מהר ולבנות פיתרונות קוד מהיום למחר כמו שצוותי פיתוח מודרניים עושים. אבל באותה נשימה חשוב לזכור את ההבדל בין סטארט-אפ לגוף ממשלתי גדול: הגישה למידע האישי והכח שיש לגופים המששלתיים מכתיבים סטנדרטים גבוהים יותר להבטחת איכות התוכנה שאתם מפרסמים.

ToCode
1 419
# לרמות את המערכת גליץ' היא פלטפורמת יצירת קוד חינמית מבית פוג קריק. היא נמצאת בשכונה כבר די הרבה זמן ויש להם מסלול חינמי ומסלול בתשלום. כמו הרבה מוצרים גם גליץ' בחרו ליצור בעיה פיקטיבית במסלול החינמי (ולפתור אותה בכסף), וכמו במקרים רבים התחיל עכשיו משחק חתול ועכבר בינם לבין המשתמשים שלהם. בגליץ' האפליקציות שאתם כותבים נסגרות אוטומטית אחרי 5 דקות של חוסר פעילות ואז בפעם הבאה שיגיע משתמש אמיתי לאפליקציה ייקח זמן להתעורר וזמן הטעינה יהיה גדול. דרך אחת לבטל את המגבלה (מה שגליץ' רוצים) היא לשלם לגליץ' 10$ בחודש. דרך יותר זולה היא להשתמש בשירות חינמי שישלח הודעה כל כמה דקות, וכך כל הזמן יש פעילות והאפליקציה לא נסגרת. לפני חודש גליץ' הודיעו שהם אוסרים על מפתחים להשתמש ב Pinging Services בפלטפורמה שלהם, וכמובן שהדבר עורר גל של פוסטים אצלם בפורום התמיכה כולל המלצות לעבור למתחרים. עכשיו אני לא פה בשביל לתת עצות לגליץ' אבל נקודה פשוטה שאני לפחות לוקח מהסיפור הזה היא ההבדל בין לספק ערך אמיתי למשתמשים לבין ליצור מגבלות לא הגיוניות רק בשביל שאנשים יצטרכו לשלם בשביל לבטל אותן. לצורך השוואה, שירות דומה של הרוקו מציע 550 שעות עבודה לאפליקציה בחינם כל חודש. אתם יכולים לשלם כדי להעלות את המספר, או לבחור בעצמכם מתי האפליקציה שלכם תהיה זמינה ומתי לא. הבחירה של גליץ' לתת יותר משאבי מיחשוב לאפליקציות שיש להן יותר Traffic נראית מוזרה ולא הוגנת, והיא שדחפה אנשים לחפש איך לייצר Traffic פיקטיבי רק בשביל לקבל יותר משאבים.

ToCode
1 419
# הבדיקות ממש הצילו אותי הפעם חמש המילים מהכותרת הן המטרה של כל חבילת בדיקות בכל מערכת. לא מעט עולה הטענה כאילו בדיקות צריכות או יכולות לעזור לנו להגיע ל Design טוב יותר, או שקוד שקל לבדוק אותו הוא קוד טוב יותר. זה לפעמים נכון אבל גם הרבה פעמים לא נכון, ובטח שאין פה סיבתיות. ״הבדיקות ממש הצילו אותי הפעם״ זה המשפט שאתם רוצים לשמוע את עצמכם או את האנשים שעובדים אתכם אומרים. זה אומר שהבדיקות שוות את המחיר של כתיבתן ושהמערכת שלכם יכולה לצמוח כי אפשר לעדכן קוד ישן בלי לשבור את ההתנהגות. ואם אתם לא שומעים אותו מספיק פעמים זה אומר אחד משני דברים: או שאתם לא עושים מספיק Refactoring, או שהבדיקות שלכם לא מספיק טובות.

ToCode
1 419
# כמה זה עולה לנו? אנחנו די טובים בהערכת עלות ותמורה כשקונים עגבניות בסופר, אבל כשעוברים לתוכנות הסיפור מסתבך. האם וובסטורם שווה יותר מ Sublime Text כי הוא עולה 59$ בשנה וסאבליים 80$ לשלוש שנים? האם בייסקמפ שווה יותר או פחות מג'ירה? וזה עוד החלק הקל כי המחיר הוא בדולרים. החלק הקשה באמת הוא להעריך כמה עולה לי להוסיף ספריית קוד או להשתמש בתוכנה חינמית. אף אחד לא יספר לכם כמה "עולה" להשתמש ב Redux לעומת MobX, או ב vim לעומת emacs. אין לנו מושג כמה זמן ייקח לך או לצוות שלך להבין את הכלי, האם תשתמשו בו נכון או האם הוא רק יסבך אתכם יותר. בשביל להבין אם כלי מסוים שווה לי אני צריך להיות מסוגל לענות כמה הוא בכלל עולה, ובספריות קוד פתוח התשובה מאוד מורכבת. שתי דוגמאות מהחיים- צוות שהתחיל להשתמש ב Redux כדי לבנות אפליקציה אבל לא הבין לגמרי איך הדברים מתחברים ובנה מוצר שמדלג על כל אופטימיזציות ה Immutable של הספריות. המוצר עובד לאט ועכשיו צריך לזרוק את כל הקוד ולהתחיל לשכתב. או צוות אחר שהוסיף ספריית JavaScript קטנה בשביל להציג טבלאות יפות עם אפשרות למיון, ובלי לשים לב גרם לכל הדפים שלהם להיטען לאט יותר וכך פגע ביחס ההמרה של האתר. בשני המקרים אם היינו יודעים מראש את המחיר אין סיכוי שהיינו מוכנים לשלם אותו בשביל התמורה הקטנה שקיבלנו. אבל קצת כמו בקניית מדפסת, מחיר הקטלוג הנמוך שכנע אותנו לקנות מוצר שהמחיר האמיתי שלו הרבה יותר גבוה. גם הערכת מחיר זו מיומנות וגם על זה אפשר להתאמן. למעוניינים קחו לעצמכם תרגיל וכל פעם שאתם מוסיפים ספריה חדשה נסו לרשום את כל ההשפעות שיכולות להיות לספריה זו על המוצר שלכם, והערכה שלכם לכמה זמן ייקח לכם להיות פרודוקטיביים עם הספריה. כשדברים עובדים כמו שצריך חיזרו לדף וסמנו כמה קרובים הייתם למספרים האמיתיים.

ToCode
1 419
# איזה כיף שיש כבר Generators ב JavaScript הרבה אומרים שאופטימיות מובילה להצלחה בחיים. בתכנות בכל מקרה האופטימיות מביאה בדרך כלל למבנה קוד שנקרא רקורסיה. יש לזה אפילו ביטוי באנגלית: Wishful Thinking. בואו נראה איך זה קורה ואיך זה קשור ל Generators. ## האתגר ניקח לדוגמא את אתגר מגדלי האנוי. באתגר יש לנו שלושה עמודים ועל העמוד הראשון מושחלות טבעות בקוטר הולך וקטן, כלומר בתחתית כל עמוד נמצאת הטבעת הגדולה ביותר, אחריה טבעת קצת יותר קטנה וכך הלאה עד הטבעת העליונה ביותר שהיא הקטנה ביותר. המשימה שלנו היא להעביר את כל הטבעות מהעמוד הראשון לעמוד השלישי כך שגם בעמוד השלישי הטבעות יהיו מסודרות מהגדולה לקטנה - אבל וזה הטריק - מותר להזיז רק טבעת אחת כל פעם, ואסור להניח אותה על טבעת שקטנה ממנה. ## הגישה האופטימית לתרגיל הזה יש פיתרון רקורסיבי ממש פשוט:
function hanoi(size, start, end, extra) {
  if (size > 0) {
    hanoi(size - 1, start, extra, end);
    console.log(`Move from ${start} to ${end}`);
    hanoi(size - 1, extra, end, start);
  }
}

hanoi(3, 'a', 'b', 'c');
אבל הבעיה עם פיתרונות רקורסיביים היא שקשה לעבוד איתם. נסו לדמיין איך יראה קוד שבלחיצה על כפתור מזיז רק טבעת אחת בין שני עמודים. הפיתרון הרקורסיבי לוקח גישה של "הכל או כלום" ובעצם רק בסיום הפעלת הפונקציה אנחנו מקבלים את כל התוצאות, ואין לנו שום נקודת התערבות באמצע. ## גנרטורים ב JavaScript פיצ'ר חמוד ויחסית לא נפוץ של JavaScript שנקרא Generators מאפשר לעקוף בקלות את הבעיה. בעזרת Generators נוכל להוסיף לפונקציה אינסוף נקודות יציאה באמצע הריצה שלה, כך שקוד חיצוני יוכל כל פעם להתקדם רק קצת עד נקודת היציאה הבאה. זה נקרא Generator כי במקום פונקציה שמחזירה ערך בודד תהיה לנו פונקציה שמחזירה סידרה של ערכים, וכל פעם בעצם יודעת איך לבנות את הערך הבא. כך זה נראה אחרי שכתוב ל Generators:
function* hanoi(size, start, end, extra) {
  if (size > 0) {
    yield* hanoi(size - 1, start, extra, end);
    yield [start, end];
    yield *hanoi(size - 1, extra, end, start);
  }
}

const solution = hanoi(3, 'a', 'b', 'c');
for (let step of solution) {
  const [start, end] = step;
  console.log(`Move from ${start} to ${end}`);
}
ארגון הקוד מחדש איפשר לי להוציא החוצה את הלולאה - כך שעכשיו יש לי שליטה הרבה יותר מדויקת על מה מופעל ומתי. הקוד יכול להדפיס רק מספר מהלכים ראשונים או אפילו יותר נחמד, להתקדם מהלך-מהלך כל פעם שמשתמש לוחץ על כפתור. ולסקרנים הכנתי דוגמא מלאה על קודפן שעושה בדיוק את זה: מתחילים עם 5 טבעות בעמוד השמאלי ביותר, כל לחיצה על הכפתור מזיזה טבעת אחת ובסוף כל הטבעות עוברות לפי החוקים לעמוד הימני ביותר. הקוד נמצא בקישור כאן: https://codepen.io/ynonp/pen/PoZgodw ואפשר לשחק איתו גם לייב בהטמעה הבאה: <iframe height="334" style="width: 100%;" scrolling="no" title="Hanoi Towers" src="https://codepen.io/ynonp/embed/PoZgodw?height=334&theme-id=20374&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true"> See the Pen <a href='https://codepen.io/ynonp/pen/PoZgodw'>Hanoi Towers</a> by Ynon Perek (<a href='https://codepen.io/ynonp'>@ynonp</a>) on <a href='https://codepen.io'>CodePen</a>. </iframe> ## וואו איזה מדליק! איפה אפשר ללמוד יותר על Generators ב JavaScript? שאלה מצוינת! קורס JavaScript ES6/7/8 שלנו כאן בטוקוד ילמד אתכם כל מה שחלמתם לדעת (וקצת יותר) על היכולות החדשות של JavaScript מהשנים האחרונות ועל דרכים יצירתיות להשתמש בהן. יש שם על Generators, async/await, פונקציות חץ, כתיב ה import וכמובן Destructuring ועוד מלא נושאים קטנים שחשוב להכיר. סילבוס, שיעורים לדוגמא וכל הפרטים בדף הקורס: קורס JavaScript ES6/7/8.

ToCode
1 419
# ארגז כלים כשרואים ארגז כלים יש פיתוי גדול לחפש את הכלי הכי חזק בארגז וללמוד אותו. הנחת העבודה היא שממילא כל מה שאפשר לעשות עם הכלים הפשוטים יותר אפשר יהיה לעשות גם עם הכלי המתוחכם. אבל הפיתוי הזה מוביל לגישות לא נכונות בתכנות- ״אין טעם ללמוד וים, אני כבר יודע טוב להשתמש ב Webstorm שעובד טוב יותר״ ״אין טעם להשקיע ב Redux, אני כבר יודע MobX שעושה הכל טוב יותר״ ״אני ממילא עובד רק עם ממשק ה git שמשולב ב IDE שלי, אז אין טעם ללמוד לעבוד איתו משורת הפקודה״ בשביל ללמוד בשמחה כלים חדשים כדאי לנו לבחור סיפור אחר לספר לעצמנו. משהו כמו ״כל כלי חדש שאני לומד פותח לי את המחשבה לדרך חשיבה חדשה״, או בדוגמאות: ״אני כבר יודע Webstorm, אז בטח יהיה מעניין לראות איך עושים פעולה דומה ב vim״ ״כתבתי כבר שני פרויקטים ב MobX, אז נראה לי שעכשיו אני מוכן להתמודד עם Redux לפרויקט הבא״ ״החודש אני הולך להשתמש ב git משורת הפקודה בלבד - מעניין מה זה ילמד אותי על הכלי שעדיין לא ידעתי״ כשאנחנו מזיזים הצידה את הרצון לסיים משימות ומשאירים מקום לסקרנות אנחנו פותחים את הדלת ללמידה ולגילוי שיטות חדשות לפתור בעיות - שיטות שלפעמים יהיו טובות יותר אפילו מהדברים המוצלחים שאנחנו כבר יודעים.

ToCode
1 419
# אבל זה רק פסיק קטן קבלו שאלת ראיונות עבודה זריזה על ריאקט שעלתה בקורס היום - מה ההבדל בין שתי הקומפוננטות האלה, ובאיזה נעדיף להשתמש:
// Component 1
function App() {
  const [value, setValue] = React.useState(_.random(1, 100));
    
  return (
    <div>
      <button onClick={() => setValue(c => c + 1)}>{value}</button>
    </div>
  );
}
// Component 2
function App() {
  const [value, setValue] = React.useState(() => _.random(1, 100));
    
  return (
    <div>
      <button onClick={() => setValue(c => c + 1)}>{value}</button>
    </div>
  );
}
נראה ממש דומה לא? ההבדל היחיד הוא בקריאה ל useState. בקומפוננטה הראשונה אני מגריל מספר אקראי ומעביר אותו ל useState, ובקומפוננטה השניה אני מעביר פונקציה שמגרילה מספר אקראי ומחזירה אותו. ולמה ההבדל חשוב? בגירסת הקומפוננטה הראשונה כל פעם שאנחנו מעלים את הערך באמצעות לחיצה על הכפתור ריאקט יקרא שוב לפונקציית הקומפוננטה כדי לקבל את ה Virtual DOM Tree המעודכן שלה. קריאה כזו תפעיל גם את _.random, תיקח את התוצאה ותעביר אותה ל useState. עכשיו בגלל שהקומפוננטה כבר נוצרה אז useState פשוט יזרוק לפח את הפרמטר שקיבל בגלל שכבר יש לו ערך בסטייט ויוצא שסתם התאמצנו להגריל מספר אקראי. בגירסא השניה המצב הרבה יותר טוב: במקום לחשב ערך כל פעם ואז להעביר אותו ל useState כדי שיחליט אם הוא צריך אותו או לא, אנחנו מעבירים ל useState פונקציה שיודעת לחשב את הערך. אם useState צריך ערך ראשוני הוא יפעיל את הפונקציה, ואם לא אז לא. בצורה כזאת כל הפעלה חוזרת של פונקציית הפקד תדלג על הגרלת המספר האקראי כי כבר יש ערך בסטייט. התבנית של העברת Callback Function ואיתה העברת השליטה לפונקציה פנימית (במקום להעביר את הערך ממש לפונקציה הפנימית) היא מאוד נפוצה בריאקט ואנחנו מכירים אותה גם מ useMemo ו useCallback. היא שימושית גם באופן כללי בתכנות ולכן כדאי להיות ערניים כשנתקלים בכתיב הזה - זה לא סתם שאנשים התלהבו לכתוב פונקציות חץ בכל מקום, אלא שבאמת יש פה משמעות מבחינת ביצועים.

ToCode
1 419
# צריכה להיות דרך ברורה אחת (ועדיף שתהיה היחידה) לעשות את זה המשפט בכותרת הוא תרגום של המשפט הבא מתוך ה Zen של פייתון. זה המקור: > There should be one-- and preferably only one --obvious way to do it. והוא נכון לכל השפות. נזכרתי בו במקרה כשדפדפתי בקוד הבא, דווקא בשפת רובי:
class ShoppingCart < ActiveRecord::Base
  has_many :products, :class_name => 'CartProduct', :dependent => :delete_all
  
    def <<(product)
        line = CartProduct.find_or_initialize_by(:product => product, :cart => self)
        # comment out to allow quantity
        # line.increment(:qty) unless line.new_record?
        line.save!
        @finalized = false

        self
    rescue ActiveRecord::RecordNotUnique
        retry
    end

    def add_with_options(product, options)
        line = CartProduct.find_or_initialize_by(:product => product, :cart => self)
        line.price = options[:price]
        line.options = options
        line.save!
        @finalized = false

        self
    rescue ActiveRecord::RecordNotUnique
        retry
    end
end
ההגיון ברור: הפונקציה הראשונה היתה הדרך המקורית בה הוסיפו פריטים לעגלת הקניות, ואז מישהו רצה לאפשר הוספת פריטים יחד עם כל מיני מתגים אז במקום לשנות את הממשק (מה שישבור קוד קיים ויצריך Refactoring) הוא פשוט הוסיף עוד פונקציה. הבעיה? עכשיו יש לנו שתי דרכים להוסיף מוצר לעגלת הקניות - וקל מאוד לטעות ולשכוח את זה למשל כשכותבים בדיקה:
  test 'cart price is the sum of all product prices' do
    @cart = create(:cart)
    @cart.products << create(:item, price: 20)
    @cart.products << create(:item, price: 50)

    assert_equal(70, @cart.price)
  end
הבדיקה נראית יפה אבל לא שווה כלום אם רוב הקוד במערכת משתמש בשיטה השניה כדי להוסיף מוצרים. צריכה להיות דרך ברורה אחת (ועדיף שתהיה היחידה) להוסיף מוצרים לעגלת קניות. רק כך אנחנו מוודאים שמי שקורא את הקוד יודע לחבר בין הבדיקה לבין הדבר שהיא באמת בודקת, שכל הקוד במערכת מתוחזק ושיש לנו אחידות בממשק. תיאורטית פיתרון קל מאוד במקרה כזה יהיה פשוט שהפונקציה הראשונה תקרא לפונקציה השניה, כלומר:
    def <<(product)
        add_with_options(product, {})
    end
וזה באמת מתקן את כפילות הקוד במימוש מחלקת עגלת הקניות אבל עדיין לא פותר לנו את הבעיה. הממשק עדיין מסורבל וכולל שתי אפשרויות בלי הבדל ברור ביניהן. יותר חשוב להבין איפה במערכת מפעילים את add_with_options ולבחור לפונקציה שם שיותר מתאים ל Use Case הספציפי שלה, ובאותה נשימה הייתי משנה גם את השם של פונקציית הוספת מוצר הישנה למשהו שיתאר טוב יותר את ה Use Case הספציפי שלה. במקרה שלנו אפשר לחשוב על:
class ShoppingCart < ActiveRecord::Base
    def add_product_sold_by_partner(product, partner_price, partner_options)
    end
    
    def add_product_from_website(product)
    end
end
בצורה כזאת השם add_product_from_website כבר רומז לנו שיש דרכים נוספות להוסיף מוצרים לעגלה ומעודד אותנו ללכת למחלקה לבדוק מהן.