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

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

הבעיה עם תכנות מונחה עצמים

05/12/2018

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

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

המשך קריאה

מצאו את ההבדלים

04/12/2018

שני קטעי הקוד האלה ברובי מדפיסים את אותו ערך ונראים מאוד דומה:

require 'set'

s = Set.new([10, 20, 30])
t = Set.new([30, 40])
puts (s | t).inspect
puts s.merge(t).inspect

בשני המקרים תוצאת ההדפסה היא:

#<Set: {10, 20, 30, 40}>

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

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

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

עכשיו אתם ועדיין ברובי- מה ההבדל בין ארבעת אלה? מה יכול להשתבש? ואיך?

arr = [10, 20, 30]

arr.inject(0) { |acc, val| acc + val }
arr.inject { |acc, val| acc + val }
arr.inject do |acc, val| acc + val; end
arr.inject(0) do |acc, val| acc + val; end

טרנספורמציות של סדרות

03/12/2018

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

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

numbers = (0..Float::INFINITY)

זאת סידרת המספרים 0,1,2,3,4,5,6 וכן הלאה עד אין סוף. אנחנו יודעים שאם נכפיל כל איבר בסידרה ב-2 נקבל את הסידרה 0,2,4,6,8,10,12 כלומר כל המספרים הזוגיים - ואנחנו יכולים להשתמש ב map כדי לעשות את זה ב Ruby:

even = (0..Float::INFINITY).lazy.map {|i| i * 2}

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

puts (0..Float::INFINITY).lazy.map {|i| i * 2}.first(10)

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

boy
girl
helllo
good
boxer

אז המילה helllo עם שגיאת הכתיב כוללת את האות l שלוש פעמים, וזה הכי הרבה שאות מופיעה במילה בכל הרשימה. אם נרצה לגשת לבעיה דרך סדרות נוכל לקחת את רשימת המילים ולהפוך אותה לרשימה של Hash-ים כך שבמקום לשמור מילה נשמור כמה פעמים כל אות מופיעה בה. במילים אחרות נשתמש ב map כדי להפוך את רשימת המילים לרשימת מבני הנתונים הבאה:

["boy", {1=>["b", "o", "y"]}]
["girl", {1=>["g", "i", "r", "l"]}]
["helllo", {1=>["h", "e", "o"], 3=>["l", "l", "l"]}]
["good", {1=>["g", "d"], 2=>["o", "o"]}]
["boxer", {1=>["b", "o", "x", "e", "r"]}]

את הרשימה השניה כבר אפשר למיין לפי המפתח הכי גדול שמופיע במילון בצד ימין, ונקבל את הרשימה הממוינת הבאה:

["helllo", {1=>["h", "e", "o"], 3=>["l", "l", "l"]}]
["good", {1=>["g", "d"], 2=>["o", "o"]}]
["boxer", {1=>["b", "o", "x", "e", "r"]}]
["boy", {1=>["b", "o", "y"]}]
["girl", {1=>["g", "i", "r", "l"]}]

בה האיבר הראשון הוא בדיוק המילה שחיפשתי.

הקוד המלא בשפת רובי שעושה את כל ההמרות, המיונים והחישובים נראה כך:

class String
  def count
    Hash.new(0).tap { |h| self.each_char { |word| h[word] += 1 } }
  end

  def icount
    counts = self.count
    each_char.group_by {|c| counts[c]}
  end
end

words = [
  'boy',
  'girl',
  'helllo',
  'good',
  'boxer',
]

puts words
  .map {|w| [w, w.icount]}
  .sort {|a, b| a[1].keys.max - b[1].keys.max}
  .reverse
  .map {|p| p[0]}
  .first

שימוש בסדרות ומיפויים שלהן כדי לפתור בעיות נותן לכם להשתמש בהרבה קוד קיים של השפה (כגון sort, reverse ו first במקרה שלנו, ופקודות רבות נוספות במקרים אחרים) כדי להתקדם בפיתרון הבעיה שלכם. המשימה הקשה כאן היא כמובן לזהות את הסידרה והמיפויים המתאימים ביותר לבעיה.

ייצוג סדרות

02/12/2018

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

bookmarks = [
    'https://www.tocode.co.il',
    'https://www.google.com',
]

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

def even(index):
    return index * 2

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

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

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

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

import sys

with open(sys.argv[1]) as f:
    for index, line in enumerate(f):
        sys.stdout.write(f"{index}: {line}")

print()

והנה גירסא נוספת הפעם שמדפיסה רק את השורות שארוכות מ-10 תווים מתוך הקובץ:

import sys

with open(sys.argv[1]) as f:
    for index, line in enumerate(f):
        if len(line) <= 10: continue
        sys.stdout.write(f"{index}: {line}")

print()

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

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

1, 1, 2, 3, 5, 8, 13, 21

אפשר היה לייצג אותה באמצעות פונקציה רגילה שנראית כך:

def fib(n):
    x, y = 0, 1
    for i in range(n):
        x, y = y, x + y

    return x

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

print(fib(8))

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

i = 0
while fib(i) < 200:
    i += 1

print(f"fib({i}) == {fib(i)}")

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

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

def fib():
    x, y = 0, 1
    while True:
        yield x
        x, y = y, x + y

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

כך נראה קוד שמשתמש בסידרה החדשה שלנו כדי להדפיס את המספר השמיני בסידרת פיבונאצ'י:

for index, val in enumerate(fib()):
    if index == 8:
        print(val)
        break

וזה הקוד שיגלה מהו הערך הראשון שגדול מ-200:

for index, val in enumerate(fib()):
    if val > 200:
        print(val)
        break

ואגב המנגנון של הגדרת סדרות קיים בהרבה מאוד שפות תכנות. הנה אותה תוכנית פיבונאצ'י בתרגום לרובי ו JavaScript - נסו למצוא את ההבדלים:

fib = Enumerator.new do |yielder|
  x, y = 0, 1
  loop do
    yielder << x
    x, y = y, x + y
  end  
end

fib.lazy.each_with_index do |val, index|
  if index == 8
    puts val
    break
  end
end

fib.lazy.each_with_index do |val, index|
  if val > 200
    puts val
    break
  end
end
function *enumerate(it, start=0) {
  for(let x of it) {
    yield [start++, x];
  }
}

function *fib() {
  [x, y] = [0, 1];
  while(true) {
    yield x;
    [x, y] = [y, x + y];
  }
}

for ([index, val] of enumerate(fib())) {
  if (index == 8) {
    console.log(val);
    break;
  }
}

for ([index, val] of enumerate(fib())) {
  if (val > 200) {
    console.log(val);
    break;
  }
}

שימו לב איך ב JavaScript גם המבנה של enumerate לא קיים בשפה ואנחנו בונים אותו באותו תחביר בניית סדרות איתו בנינו את סידרת פיבונאצ'י.

מצב פיתוח לעומת מצב ייצור

01/12/2018

המערכת שלכם לא מתנהגת אותו דבר במצב פיתוח ובמצב ייצור. במצב פיתוח יש לנו הודעות שגיאה מפורטות, אפשרות להתחבר בשניה עם Debugger, אולי Live Reload כדי שכל שינוי מיד יופיע על המסך. במצב ייצור (Production) עוברים להציג הודעות שגיאה גנריות ולרשום את השגיאות בלוג, מכווצים את כל הקוד וסוגרים את כל אפשרויות החיבור כדי שירוץ מהר ובצורה מאובטחת.

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

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

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

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

מה השיטה שלכם?

זה לא בשבילי

30/11/2018

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

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

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

האם זה קצב טוב? האם זה משנה?

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

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

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

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

כמה זמן ייקח לך ללמוד לתכנת? כמה שתבחר. והרמה תעלה ככל שתיתן לזה יותר זמן.

עוד מילה טובה על jQuery

קטע הקוד הבא עובד טוב ב Chrome, Firefox ו Edge וגם על מובייל לא עשה לי עדיין בעיות. רק IE צריך להיות מיוחד:

var form = document.querySelector('#myform');
var fd = new FormData(form);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/submit');
xhr.send(fd);

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

וכך נראה טופס שעבורו הקוד הזה נכשל:

<form id="theform" action="/people" method="post">
  Name: <input type="text" name="person[name]" value="John Appleseed">

  <input type="radio" name="person[sex]" value="male"> Male
  <input type="radio" name="person[sex]" value="female"> Female
  <input type="radio" name="person[sex]" value="not_specified"> (Not Specified)

  <input type="submit" name="commit" value="Create Person">
</form>

מה שמיוחד כאן זה שיש Radio Button בתור האלמנט האחרון בטופס. וברגע שהבנתם את זה ה"תיקון" קל מאוד - פשוט תוסיפו לטופס שדה input נוסף אחרי ה Radio Button. הטופס הזה למשל עובר בכל הדפדפנים:

<form id="theform" action="/people" method="post">
  Name: <input type="text" name="person[name]" value="John Appleseed">

  <input type="radio" name="person[sex]" value="male"> Male
  <input type="radio" name="person[sex]" value="female"> Female
  <input type="radio" name="person[sex]" value="not_specified"> (Not Specified)
  <input type="hidden" name="dontcare" value="dontcare" />

  <input type="submit" name="commit" value="Create Person">
</form>

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

שימוש ב jQuery.serialize היה מונע את הבאג וחוסך לי את המחקר, הגילוי העצוב וה Work Around שלא בטוח שמכסה את כל המקרים. אז כן, גם ב 2018, אם אתם יכולים להרשות לעצמכם - שימו jQuery.

אגב תיעוד של הבאג קיים ברשת כבר מ 2014 למשל בקישור הזה: https://blog.yorkxin.org/2014/02/06/ajax-with-formdata-is-broken-on-ie10-ie11.html

פערי ידע

28/11/2018

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

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

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

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

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

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

הדבר היחיד שחשוב

27/11/2018

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

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

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

  2. נוח מאוד לשמור את הלוגים גם במערכת לוגים חיצונית שאפשר לגשת אליה אפילו מהטלפון. יש הרבה כאלה בשוק ובמחירים לא גבוהים (חפשו cloud log storage בגוגל).

  3. כדאי להחזיק סביבות בדיקה זמינות שיאפשרו לסמלץ את הבעיה של הלקוח. אני שומר מספר מכונות וירטואליות עם מערכות הפעלה שונות וגירסאות שונות לפי מה שהלקוחות הגדולים שלי משתמשים. בנוסף יש את Browser Stack שיכולים לתת פיתרון טוב למובייל.

  4. נוח מאוד לשמור לוג גם של קוד ה JavaScript שרץ אצל הלקוחות שלכם. אפשר לחבר אותם ישירות למערכת הלוגים החיצונית אבל יותר קל להשתמש בהודעת Ajax לשרת שלכם שיתעד את הכל בצורה מסודרת.

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

  6. וכמובן אחרי שכבר מצאתם את הטעות לא לשכוח להוסיף בדיקה אוטומטית כדי שהיא לא תחזור על עצמה בתיקון הבא.

שיקולי עלות/תועלת למתכנתים

26/11/2018

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

(וכשאני אומר יותר מוצלח מה Spec אני מתכוון הרבה יותר מוצלח ממה שהמנהל שלך רוצה).

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

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