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

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

דוקר: מה שלא צריך לדעת

25/09/2021

הקובץ הבא הוא קובץ Dockerfile תקין לגמרי שאני משתמש בו במצב פיתוח לפרויקט Node.JS:

FROM node:16

WORKDIR /app

הוא עובד אם מצמידים אליו קובץ docker-compose.yml שממפה את תיקיית הפרויקט לתיקיית /app על הקונטיינר, מגדיר את הפקודה להרצה ופותח את הפורטים המתאימים.

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

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

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

  1. חשוב להכיר את ה "מה" - מה זה אומר לרוץ בתוך קונטיינר, מה המגבלות של הקונטיינר.

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

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

  4. חשוב להבין איך נשמר מידע רגיש, מה זה Secrets ואיך לנהל אותם.

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

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

תכסו את ששת הסעיפים האלה ולאנשי ה Devops אצלכם יהיו חיים הרבה יותר קלים כשהם יבואו להפוך את קבצי ה Dockerfile הקטנים שתכתבו לקבצי הגדרות אמיתיים, ואת קבצי ה docker-compose.yml הקטנים שלכם להגדרות קוברנטס.

איך נשבר לי אתמול הבלוג

24/09/2021

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

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

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

/var/lib/gems/1.9.1/gems/nokogumbo-1.1.2/lib/nokogumbo.rb:24: [BUG] Segmentation fault
ruby 1.9.3p448 (2013-06-27 revision 41675) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0026 p:---- s:0094 b:0094 l:000093 d:000093 CFUNC  :parse
c:0025 p:0094 s:0090 b:0090 l:000089 d:000089 METHOD /var/lib/gems/1.9.1/gems/nokogumbo-1.1.2/lib/nokogumbo.rb:24
c:0024 p:0021 s:0086 b:0086 l:000085 d:000085 METHOD /var/lib/gems/1.9.1/gems/nokogumbo-1.1.2/lib/nokogumbo.rb:8
c:0023 p:0016 s:0082 b:0082 l:001fb8 d:000081 EVAL   (irb):4
c:0022 p:---- s:0080 b:0080 l:000079 d:000079 FINISH
c:0021 p:---- s:0078 b:0078 l:000077 d:000077 CFUNC  :eval
c:0020 p:0028 s:0071 b:0071 l:000070 d:000070 METHOD /usr/lib/ruby/1.9.1/irb/workspace.rb:80
c:0019 p:0033 s:0064 b:0063 l:000062 d:000062 METHOD /usr/lib/ruby/1.9.1/irb/context.rb:254
c:0018 p:0031 s:0058 b:0058 l:001b48 d:000057 BLOCK  /usr/lib/ruby/1.9.1/irb.rb:159
c:0017 p:0042 s:0050 b:0050 l:000049 d:000049 METHOD /usr/lib/ruby/1.9.1/irb.rb:273
c:0016 p:0011 s:0045 b:0045 l:001b48 d:000044 BLOCK  /usr/lib/ruby/1.9.1/irb.rb:156
c:0015 p:0144 s:0041 b:0041 l:000024 d:000040 BLOCK  /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:243
c:0014 p:---- s:0038 b:0038 l:000037 d:000037 FINISH
c:0013 p:---- s:0036 b:0036 l:000035 d:000035 CFUNC  :loop
c:0012 p:0009 s:0033 b:0033 l:000024 d:000032 BLOCK  /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:229
c:0011 p:---- s:0031 b:0031 l:000030 d:000030 FINISH
c:0010 p:---- s:0029 b:0029 l:000028 d:000028 CFUNC  :catch
c:0009 p:0023 s:0025 b:0025 l:000024 d:000024 METHOD /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:228
c:0008 p:0046 s:0022 b:0022 l:001b48 d:001b48 METHOD /usr/lib/ruby/1.9.1/irb.rb:155
c:0007 p:0011 s:0019 b:0019 l:000fd8 d:000018 BLOCK  /usr/lib/ruby/1.9.1/irb.rb:70
c:0006 p:---- s:0017 b:0017 l:000016 d:000016 FINISH
c:0005 p:---- s:0015 b:0015 l:000014 d:000014 CFUNC  :catch
c:0004 p:0183 s:0011 b:0011 l:000fd8 d:000fd8 METHOD /usr/lib/ruby/1.9.1/irb.rb:69
c:0003 p:0039 s:0006 b:0006 l:0008b8 d:0011c8 EVAL   /usr/bin/irb:12
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH
c:0001 p:0000 s:0002 b:0002 l:0008b8 d:0008b8 TOP   

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

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

# coding: utf-8
require 'rails_helper'

describe 'Blog', type: :system do
  before do
    create(:blog_post, slug: 'post1', title: 'post 1')
    create(:blog_post, slug: 'post2', title: 'post 2')
  end

  it 'shows index page' do
    visit blog_index_path
    expect(page).to have_title 'tocode | הבלוג של ינון פרק'
  end

  it 'shows a post page' do
    visit blog_path(id: :post1)
    expect(page).to have_title 'tocode | post 1'
  end
end

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

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

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

הזדמנויות ומתי לנצל אותן

23/09/2021

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

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

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

והתשובה לצערי היא שזמן לא עובד ככה.

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

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

חמישה דברים שאהבתי ב Vite ואחד שממש לא

22/09/2021

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

בעזרת Vite תוכלו לבנות פרויקטים של הפריימוורקס המובילים - Vue, React, Svelte, preact ועוד אחד שנקרא lit-element, וכמובן פרויקט וונילה ללא פריימוורק. בכל אחד מהמקרים תקבלו תבנית לפרויקט וסקריפטים להגשה במצב פיתוח או בניה במצב פרודקשן.

הנה 5 דברים שאהבתי ואחד שלא כל כך בפרויקטי Vite שיצרתי:

המשך קריאה

לא לדאוג, אני יודע רובי

21/09/2021

החידה הבאה הגיעה אליי במקרה בטלגרם:

מבלי לשנות את סדר המספרים, עליכם להציב 3 סימני חיבור או חיסור ביניהם כך שתקבלו משוואה נכונה: 100 = 9 8 7 6 5 4 3 2 1

אחרי כמה דקות של Brute Force בראש הבנתי שעדיף לתת למחשב לשבור את הראש על זה, וזה הזכיר לי כמה נעים לכתוב קוד ברובי.

המשך קריאה

אז מה באמת ההבדל בין Authorization ל Authentication?

20/09/2021

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

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

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

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

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

exports.getTasks = function(user) {
    mongoClient.collection('tasks').find({ owner: user }).toArray();
}

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

כללי Authorization יכולים להיות גם מפורשים, לדוגמה את אותה פונקציה יכלתי לכתוב כך:

exports.getTasks = function(user) {
    const tasks = mongoClient.collection('tasks').find({ owner: user }).toArray();

    authorize(user, 'read', tasks);

    return tasks;
}

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

ב Node.JS, הספריה passport מספקת דרך פופולרית לבצע AuthN. הספריה cancan מספקת פיתרון Authorization ליישומי ווב, אבל היא הרבה פחות פופולרית מ Passport. אולי כי הרבה אנשים מעדיפים לבנות פיתרון כזה בעצמם.

לא נחשב

19/09/2021

"הוא היה פרילאנסר 5 שנים, זה לא נחשב ניסיון"

"היא כתבה 8 שנות ניסיון, אבל תכל'ס רק 2 מתוכן בחברה רצינית. השאר לא נחשב"

"פחות מ-500 מילים? זה לא נחשב פוסט"

"קורס פייתון בלי פנדס? זה לא נחשב"

"כל הפרונטאנד הזה לא באמת נחשב תכנות"

"כתבת כלי ש 90% מהמתכנתים בגוגל משתמשים? נו, זה לא נחשב אם אתה לא יכול גם להפוך עץ בינארי"

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

הדברים הקטנים

18/09/2021

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

הנה דוגמה לשינוי קוד פשוט מסוג זה. היה לי פרויקט knex בו לכל טבלה בבסיס הנתונים היה קובץ Repository שכלל קוד לפעולות בסיסיות עם הטבלה. לדוגמה עבור טבלה מדומיינת של משימות היה לנו קובץ שנראה בערך כך:

function create(db, task) {
  return db('tasks').insert(task).then(([createdId]) => ({ ...task, id: createdId }));
}

function select(db, ...columns) {
  return db('tasks').select(columns);
}

function destroy(db, task) {
  return db('tasks').where('id', task.id).del();
}

function update(db, which, what) {
  return db('tasks').where('id', which.id).update(what);
}

module.exports = { create, select, destroy, update };

אנשים אהבו את זה כי בתוכנית הראשית היית יכול לכתוב דברים כמו:

const newTask = await tasks.create(db, { text: 'new task', done: false });
await tasks.update(db, newTask, { done: true });

console.log(await tasks.select(db, '*'));
await tasks.destroy(db, newTask);

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

ה Refactoring במקרה הזה היה קסום כמו שהיה פשוט. יוצרים קובץ אחד בשם repo.js עם הקוד הבא:

module.exports = function(name) {
  function create(db, item) {
    return db(name).insert(item).then(([createdId]) => ({ ...item, id: createdId }));
  }

  function select(db, ...columns) {
    return db(name).select(columns);
  }

  function destroy(db, item) {
    return db(name).where('id', item.id).del();
  }

  function update(db, which, what) {
    return db(name).where('id', which.id).update(what);
  }

  return { create, select, destroy, update };
}

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

module.exports = require('./repo')('tasks');

וגם מי שצריך יותר יכול לקחת את 4 הפעולות מ repo ולהוסיף עליהן פעולות חדשות.

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

(נ.ב. אם אתם כבר עובדים ב knex ורוצים להרחיב אותו עם יכולות של ORM שווה לבדוק את פרויקט Bookshelf.js שעושה בדיוק את זה)

תכנות מונחה דיבאגר

17/09/2021

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

אחת הבעיות עם תכנות מונחה דיבאגר היא שזו שיטת עבודה שמשמידה את עצמה.

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

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

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

פייק

16/09/2021

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

התרגיל הוא פשוט ועוזר להתאמן על פעולות חילוק במספרים שלמים ושארית. אם נישאר רק עם המספרים הקטנים מ-40 אפשר לנסות את הפיתרון הבא:

symbols = (
        [10, 'X'],
        [5, 'V'],
        [1, 'I' ]
        )

def convert_to_roman(number):
    result = ''
    for i, r in symbols:
        result += r * (number // i)
        number = number % i
    return result

קוד הבדיקה הבא משתמש בפיתרון ומראה שהוא עובד לפחות על 5 מספרים:

result = {
        28: 'XXVIII',
        12: 'XII',
        6: 'VI',
        32: 'XXXII',
        21: 'XXI',
        }

for k, v in result.items():
    if convert_to_roman(k) != v:
        raise Exception(f"Conversion failed for {k}. Expected: '{v}'; Got: '{convert_to_roman(k)}'")

print("All OK")

אבל כשננסה להפעיל את ההמרה על מספר כמו 19 נקבל שגיאה. ההמרה הנכונה של 19 לספירה רומית היא XIX אבל אצלנו הוא יופיע בתור XVIIII. איך מתקנים?

כיוון אחד יכול להיות לנסות לשכנע את המחשב לבדוק כל מיני אפשרויות לבנות את המספרים, ולכתוב את הקוד שמזהה ש 19 הוא 20-1 בדיוק כמו שהוא 15+4 אבל ש 20-1 יותר קצר לכתיבה ולכן כדאי להמיר לזה.

כיוון יותר קל הוא לרמות:

symbols = (
        [10, 'X'],
        [9, 'IX'],
        [5, 'V'],
        [4, 'IV'],
        [1, 'I' ],
        )

אם טבלת הסמלים שלי כוללת גם את 9 ו-4, אז כשהפונקציה תתקל ב 19 היא קודם כל תיקח את ה 10 ותהפוך אותו ל X, ואז את ה 9 שנשאר תהפוך ל IX. הפונקציה לא מספיק חכמה בשביל להבין ש 9 זה עשר פחות אחד, אבל גם לא צריכה להיות. את החוכמה אפשר להשאיר לרומאים.