קטגוריה: פיתוח

מציאת רשומה לפי התחלה מדוייקת ב PostgreSQL

הנה בעיה מעניינת – כיצד אני מביא רשומה המתאימה ביותר להתחלה כלשהי, כאשר ההתחלה משתנה באורך/תווים שלה?

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

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

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

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

הדגמה של טבלת קידומות:

להמשיך לקרוא

מי הרג את התוכנה שלי?

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

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

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

מחקר

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

התחלתי לשחק קצת עם הגדרות systemd בכיוונים של type ב service שיצרתי, אבל זו לא היתה הבעיה.

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

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

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

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

במקרה באחד הבאגים, היתה שאלה אשר ביקשה לקבל מידע על הגדרות בנושא של systemd.kill ושיש לבדוק אותו.

כאשר התחלתי לחפש על הנושא במנוע חיפוש, גיליתי כי מרבית השאילתות במנוע החיפוש היו איך "להרוג" את התוכנה עם systemd ואיך שולחים אותות (סיגנאלים) עם systemd, ופתאום האתר החביב על כולם stack-overflow גילה לי שיש אופציה ששוה לבדוק בשם KillMode (ואתם חשבתם כי דף 3-4 במנוע חיפוש זה מקום שפתרונות באים למות בהם).

חיפוש של systemd.kill עם KillMode הוביל אותי לתוצאות רלוונטיות יותר.

מציאת פיתרון

כאשר נכנסתי ל system.kill, יש הסבר שלדעתי לא מספיק טוב וממצא לאיך systemd מתמודד עם "הרג" של תהליכים בכל מצב.

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

מסתבר כי ברירת המחדל היא להרוג "קבוצה" של תהליכים, כלומר התהליך שלי והבנים שלו, גם אם אני הרצתי עם exec ומנותק לגמרי מהבנים, עצם זה שהתהליך הריץ עוד דברים בזמן עליה, תגרום לכך שהיציאה של התהליך (במקרה הזה), תהרוג את הבנים (תודה stack overflow להסבר).

הפיתרון היה לשים

KillMode=process

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

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

מבט לאחור

היו לי מספר דברים מאוד מביכים:

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

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

שכתוב הגדרות עבור neovim 0.5

ב2017 עברתי מ VIM לפרויקט חדש (יחסית) בשם neovim אשר שם לו למטרה לבצע refactoring ב VIM ולהסיר קוד ישן, לתקן קוד קיים בעייתי, בו בעת להמשיך את VIM והתפתחות שלו.

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

לאחרונה שוחררה גרסה 0.5 ל neovim אשר מביאה המון חידושים ותוספות.
בין היתר גם:

  • חלון Popup.
  • תמיכה מלאה ב Lua כשפה first class (ביחד עם VimL).
  • תמיכה ב LSP.
  • תמיכה ב tree-sitter.
  • תוספות UI (למשל ל Popup message).

ועוד.

השינויים שלי

בחודשים האחרונים עברתי לעבוד עם coc.nvim אשר מנסה לספק השלמה אוטומטית, תמיכה ב LSP, תמיכה ב Linting ואפילו Fuzzy Finder, וכל זה בשימוש של תוספים המבוססים על Visual Studio Code וכתוב ב node.js.

הבעיות שלי עם coc.nvim שיש לי איתו צרות של CPU גבוה מאוד ובנוסף זיכרון שמתמלא כאשר אני עובד עם קבצים של אלפי שורות קוד שכולם פתוחים ב buffer כי אני עובר בין אחד לשני הרבה.
בנוסף לזה, הייתי מוצא מספר process של LSP שונים לאותה שפה (בפורטים שונים) עם הודעות שגיאה ב messages כשהוא מנסה להעלות אותם ונכשל (עד שמוצא פורט פנוי).

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

nvim הנוכחי שלי

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

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

השינוי מאפשר לי להתאים כלים עבור neovim עם היכולות שלו ולמעשה אני מסיר תוספים לא נדרשים שהייתי צריך אותם ב vim 7 כי לvim עצמו לא היתה תמיכה בדברים.

עבור צביעת התחביר בחרתי לעבור לשימוש ב tree-sitter, תוסף שאמור לספק תמיכה מדוייקת יותר מאשר הצביעה ה"רגילה" המשתמשת בregex, אשר נמצא בשימוש אף הוא אצלי, במידת הצורך.

השימוש שלי ב nvim-lsp מאפשרת לי לקבל הגדרות ברירת מחדל (שאני יכול לעקוף) למנועים מוסימים ובכך אני לא זקוק ל ALE לדעת על בעיות מצד אחד, ומצד שני אין לי צורך ב coc להשלמה.
זה מאפשר לי גם לספק כלי חדש לראות symbols שונים עם LSP בנוסף ל ctags למשל, ולכן אני משתמש ב vista.vim במקום Tagbar.

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

עבור מערכת ה Fuzzy Finding החלטתי לעבוד עם Telescope. התוסף מכוון אצלי לעבוד עם ripgrep בברירת מחדל, אבל יש לי גם תמיכה ל fzf במידת הצורך.

יכולות telescope

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

השלמה אוטומטית, פלוס הדפסת הcommit האחרון (שלא קיים) בשורה שאני עומד עליה

בנוסף, ל snippets שהייתי עובד איתם בצורה רגילה, החלטתי להוסיף לשם שינוי גם snippets לbolier plate שאני עושה המון בשפות שונות ואין לי snippets עבורם. החיבור עם ddc עובד ממש יפה לרוב.
מערכת ה Snippets שבחרתי היא UltiSnips.

יש תמיכה ממש טובה ב git גם הצגה של מה נוסף/שונה/נחמק וכו'. ואפילו blame על השורה שאני עומד בה.

הצגת שגיאות מבסיס LSP

סיכום

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

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

ללמוד לתכנת בגישה שונה

מזה זמן (שנתיים או שלוש) שאני רוצה ללמוד את שפת Rust, היות ויש לי שימוש בשבילה – להעיף את ++C מדברים שאני צריך לפתח.
הבעיה שגרמה לי לא לעשות זאת עד עכשיו זה זמן.

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

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

להמשיך לקרוא

הדגמה ליצירת קובץ wav (כמעט) מאפס

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

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

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

מה הוא struct?

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

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

למשל במידה ויהיה הקוד הבא:

type S struct {
  B1 byte
  B2 byte
}

יש מבנה נתונים של 2 בתים: B1 ו B2. כלומר יש לי רשומה של 2 בתים וזה גודל הרשומה.

כלומר פעולת sizeof (אופרטור/פונקציה – בהתאם לשפה המחזירה גודל בבתים של מבנה או טיפוס נתונים) יחזיר לנו "2" כלומר מבנה הנתונים מחזיק 2 בתים.

אם הייתי מוסיף גם שדה מסוג uint64 מבנה הנתונים היה עכשיו בגודל 16 בתים.

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

הייצוג של הרשומה למעלה זהה לדבר הבא:

var B1, B2 byte

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

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

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

קצת על קובץ Wav(e)

קובץ wav הוא סוג של מיכל (כלומר container) עבור מידע מסוג קול (אודיו) בלבד מבית מיקרוסופט.
הקובץ יודע להחזיק מידע raw כלומר מידע שלא עבר עיבוד (PCM ו ADPCM) וכן של codec. כאשר אנחנו מדברים על codec אנו מדברים על ייצוג של מידע בצורה מעובדת, כדוגמת קיבוץ מידע, סימון מידע כדוגמת מתי מתחיל ונגמר שקט או כל דבר אחר אשר מכיל את המידע הגולמי בצורה מעובדת שיכולה גם להיות קטנה יותר מהמקור או לציין בצורה דיגיטלית מידע שלא ידוע בצורה "טהורה" של קול או ווידאו.

בנוסף לבסיס, הפורמט של קובץ ה wav יודע לעבוד עם "חתיכות" מידע, לרוב באמצעות מבנה בשם RIFF.
פורמט קובץ הwav יכול להיות כחלק מcontainer אחר בשם AVI אשר גם הוא הגיע מבית מיקרוסופט, והפורמט יודע לשמור מידע של תמונה נעה (ווידאו) וכן אודיו לרוב בפורמט של WAV עם RIFF.
כאמור, קובץ הWAV נוצר על ידי חברת מיקרוסופט, והוא למעשה פורמט מאוד "בסיסי" (אך לא פשוט) במערכת ההפעלה של החברה – Windows.

להמשיך לקרוא

hell אל dll (בGo) – חלק ראשון

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

יצרתי באמצעות שפת C++‎ (וקצת C) איזשהו DLL המבצע קיצורי קוד עבורי שבכל דרך אחרת הייתי צריך לממש הרבה COM בשפת Go – כאב ראש עם הרבה boilerplate code של Vtbl ‏(Virtual Function Table) ומימוש Interfaceים שלמים, כולל ירושות שלהם מinterface אחרים.

אני מאוד אוהב את שפת Go אבל יש לי גם ביקורת קשה כלפיה – הביקורת העיקרית שלי (לצורך הפוסט כי יש לי יותר מביקורת מאחת) – הוא שמצד אחד ניסו לספק לי קוד "גבוה" שלא מעניין אותו מערכת ההפעלה, כאשר מהצד השני, רואים שהוא נבנה עבור Unix/Linux וכאשר אנו נמצאים במערכת הפעלה שונה כדוגמת Windows מערכת ההפעלה מרגישה שם כ second class citizen.

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

void GetBuffer(type_t * in, const char **buffer, const unsigned int * length);

אני מקבל struct שמכיל מידע ומחזיר בתמורה buffer של בתים (במקרה הזה).

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

הבעיה היא שבזמן טעינת DLL בזמן ריצה בצורה דינאמית עם LazyDLL (למשל) צריך להעביר פרמטרים וזה דורש מאיתנו להשתמש ב unsafe.Pointer בשילוב של טיפוס בשם uintptr.

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

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

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

...
outBuffer := make([]byte, 2 << 24)
var length uint
...
getBufferFunc.Call(
  uintptr(unsafe.Pointer(&MyType)),
  uintptr(unsafe.Pointer(&outBuffer)),
  uintptr(unsafe.Pointer(&length)),
)
...
saveToFile(outBuffer[:length])
...

בעצם "נזכרתי" כי מערך בC יכול להיות בעצם מצביע אשר אם אני מקבל את האורך שלו, אני יכול לקרוא רק את מה שאני צריך ולא צריך מעבר.

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

הכנסתי לdll דגל אשר במידה ומדובר בגרסת פיתוח הוא גם כותב את buffer בעצמו לקובץ, וכך גם ידעתי להשוות בין הדברים (היות וקומפיילר של Go אינו יודע ליצור debug symbols בWindows) בצורה "טיפשה". וזאת לאחר ש delve לא הסכים לעבוד לי.

בקוד רגיל, ולא בהדגמה שכזו, חשוב גם לבדוק שגודל length אינו גדול יותר ‎ 2 << 24 לשם כך אפשר לבדוק את ה cap של ה slice ואין צורך לחשב מחדש.

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

תכנות בווינדוז – 2020-2021

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

מי שלא מכיר, הייתי בעבר מתכנת Windows, פיתחתי בעיקר עם VB/VBA ודלפי, כך שהזיכרון שלי על win api הדהים אותי שעדיין קיים ורלוונטי יחסית.

מצד אחד מיקרוסופט מנסה להיפתר לאט לאט מקריאות ישנות, ומצד שני היא מנסה לתת דגש רחב יותר כלפי העבודה מול COM ומשפחתו.
ואם זה לא מספיק, אז מיקרוסופט מנסה לספק מענה לעוד שפות תכנות כדוגמת Javascript באמצעות מנגנון הנקרא Universal Windows Platform (או UWP בקיצור).

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

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

המערכת שאני כותב היא cross platform וכתובה ב Go. בלינוקס היה לי מאוד קל להתחבר לlibc כאשר אין לי binding או חבילה מוכנה בGo (ואין לי את הזמן ליצור את התמיכה בכל הapi set ואני צריך רק פונקציות וstruct מסוימים מאוד), אבל בווינדוז אני מוצא את עצמי עובד מאוד קשה מול COM או פשוט ליצור ב VS איזשהו DLL שאני כותב ב C/++ ‎ (משלב בין שניהם כי לא באמת רגיל לתכנת ב ++C).

התסכול הבא, היה לאבחן בעיות ותקלות. הנה בעיה מוזרה – כלל החומורת מכילות דריברים המספקים לי פידבק אודות מעשים מסויימים באמצעות Windows Messages.
אבל – חלק מהחומורת ו/או דריברים לא מעלים את ה events וחלקם כן.
יותר מזה, אין ב DC שום דבר המגביל זאת, וכן אין משהו שחוסם משהו.
אמנם יש דרכים כנראה לפי התיעוד להכריח את ווינדוז לעשות זאת, אבל זה משהו שאסור לי לעשות בשל רגולציות שונות עבור אותה מערכת (לפי מה שהוסבר לי) ולכן אם זה לא עובד אין לך תמיכה במערכת.

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

בזמן התמודדות הבעיות הבנתי למה לינוקס נוח עבורי כל כך, בזמן שפיתוח בWindows ממש לא (אפל איפשהו באמצע). בעיה מוזרה שהיתה לי – יוצר קובץ (בינארי ו/או טקסט) כותב אליו, עושה close ואולי אפילו flush לפני – מנסה לגשת אליו ממערכת אחרת והקובץ לא קיים, או בגודל 0.
הבעיה היתה שבעוד שה File Descriptor (או Handle בWindows) שוחרר, מערכת ההפעלה עדיין לא כתבה את המידע או כל המידע לקובץ בפועל. עד עכשיו אני לא יודע למה, וזה גם לא מתרחש כל הזמן.

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

עוד בעיה – יצרתי dll ב ++C (או חצי ++C וחצי C מבחינת התחביר), לדעתי ייצאתי (כלומר export) את כל הפונקציות שרציתי, אבל בפועל לא הצלחתי לעשות לכולן link. בלינוקס אני יכול עם objdump לראות מה ייצאתי בפועל, אבל בווינדוז? אני צריך כלי חיצוני שלא מגיע עם VS.

ניסיתי גם להדפיס למסך תוכן שהוא utf-8 לא הכל מוצג. עכשיו שאלות – האם זה פונטים? האם זה באג שלי?
הcmd לא כזה ברור אם הוא מסוגל להציג את זה בכלל (הבעיה היתה הגדרות locale של פונטים). למשל בלינוקס הרצה של טקסט מול od -x תעזור לי מאוד לראות ב hex האם מדובר בבעיה שלי או משהו אחר.

כאשר ניסיתי לעבוד ב VS עם breakpoints להבין את הבעיה למעלה הגעתי למצב שהווינדוז נתקע עם 100% cpu במשך 45 דקות. מה שקרה בסוף זה אתחול כפוי של מערכת ההפעלה כי שום דבר לא הגיב לי.
זה נמשך 45 דקות כי הייתי צריך להתמודד בין לבין עם משהו אחר אז השארתי את זה לרוץ בזמן שעברתי וכשחזרתי זה עדיין היה תקוע.

כאשר אני כותב מערכות לווינדוז, אני מרגיש כמו משהו זר שאיכשהו בכל זאת זה עובד, אבל בקסם, בעוד שלינוקס זורם לי מאוד למרות שאני עובד ב ‎(neo)vim, וטרמינל בעיקר במקום איזשהו IDE גדול.

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

זו כמובן התחושה שלי בנושא.

אם או אחרת?

לאחרונה היה לי חצי דיון בטוויטר לגבי מתודולוגיה תכנותית – האם יש מצבים בהם יש מקום להשתמש בelse כאשר יש שאלת תנאי.

דעת האדם שלו הגבתי היא שאין מצב מבחינה לוגית שצריך אי פעם else. הוא הראה דוגמאות מאוד מופשטות (בעיני) איך הוא פותר את הנושא.

למשל ראו את קוד גו הבא:

...
a := 1
if b > 12 {
  a = 2
}
...

למעשה הצבתי ערך ברירת מחדל ל a של 1 ואם משתנה בשם b גדול מהמספר 12, אז אציב את הערך 2.

כאן אני יכול להסכים כי אין צורך ב else.

אבל לא תמיד המצב הוא כזה פשוט:

...
err = nil
if b > 12 {
   callOneFunc()
} else {
  err = callSecondFunc(b)
}
...

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

אבל אחרת, הרץ פונקציה המועבר אליה b והחזר שגיאה (במידה ויש).

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

אבל, יש מצבים בהם אני יכול לוותר לגמרי לשאלת התנאי:

...
caller := map[bool] func(i int)error {
  true: callOneFunc,
  false: callSecondFunc,
}

err = caller[b > 12](b)
...

כאן יצרתי מערכת של רישום פונקציות שונות על ידי שימוש ב hash map. כאשר לא משנה מה התוצר, אני יכול להריץ פונקציה בעלת אותה חתימה, אבל ללא צורך בתנאי כלשהו, אלא רק בתשובה בוליאנית. כמובן שאפשר גם לייצג ככה switch case ללא default, כל עוד האיבר בהכרח חייב להיות קיים.

אז מה "נכון"?

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

אבל נגיד ו2 פונקציות בעלות אותה חתימה. אחת תבצע פעולת HTTP והשניה תריץ קובץ מקומית. עצם הפעולות עצמן הן יקרות, אבל הן גם שונות לגמרי למרות שהתוצר אולי יהיה זהה.

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

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

...
func ExecIfBigger(b int) {
  if b > 12 {
    callOneFunc()
  }
}

...

ExecIfBigger(b)
if b <= 12 {
  err = callSecondFunc(b)
}
....

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

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

מה דעתכם בנושא?

אופטימיזצית פרוייקט בזמן קורונה

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

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

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

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

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

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

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

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

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

יצרתי סוג של טיימר הסופר עד כמות מסויימת של מילי שניות ובמידה והגיע אותו timeout לפני שהתוצר הסתיים, הייתי מדווח שגיאה חזרה.
במידה והסתיים, המידע המתאים נשלח כי הוא התקבל חזרה, ובטיימר לא היה באמת sleep כלשהו אלא תפיסה של "אירועים" על ידי שיחרור מיוטקסים או לחכות לmutex מסוים להשתחרר. הידע מתי לשחרר אותו התבצע על ידי שמירת ה payload שמגיע בstreaming pipe עד אשר תו מסויים מגיע, או אורך מסוים הגיע (הראשון מבניהם). במידה ועד שזה מתבצע הגיע הזמן לשחרר, אז mutex אחר שספר ticks פעל כאשר הבדיקה היתה על אחד משניהם.

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

לאחר השינוי הזה, המערכת התחילה לחזור אלי לרוב אחרי 10-200 מילי שניות, כאשר 500 מילי שניות היו לתוכן גדול בהרבה ממה שאותו משרד ממשלתי שולח אלי.

גרמתי למערכת לרדת לפעמים לפחות מ10 מילי שניות כאשר מקודם לכן הממוצע הנמוך עמד על 500 מילי שניות והגבוה על 700 מילי שניות.

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

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

היה שלום פרל, ותודה על הסימנים

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

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

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

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

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

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

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

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

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

זה סוג של סיום תקופה כזו. אז היה שלום ותודה על סמלים

לשחק עם כובש ההשלמות

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

השלמת תוכן ב nvim

החלטתי בהמלצת מאיר לנסות את coc – שפירושו הוא Conquer of Completion .
התוסף הוא בפני עצמו מאוד מעניין. הוא משתמש מאחורי הקלעים בחלק מכובד של תוספים המגיעים מ Visual Studio Code של מיקרוסופט, בנוסף לשימוש ב LanguageServer.
הוא מחזיק בתמיכה של "חלונות צפים" שנכנס לשימוש ב neovim רק לאחרונה:

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

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

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

עד כאן הכל טוב

אבל, שמתי לב למספר בעיות חדשות שצצו לי במערכת. למשל בגלל שcoc משתמש ב node, הוא שוקל יחסית הרבה בזמן ריצה על ה cpu ולא רק על הזיכרון.

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

שורה תחתונה

אחרי חודש וחצי של משחקים, אני חייב לציין שאני מאוד אוהב את coc, וחושב לייצב אותו יותר ב"הפצה" שלי של vim.

הקוד שאתם רואים בתמונה הוא חלק מפרוייקט שפתחתי בגיטהאב שכולכם יכולים להנות ממנו – תמיכה בספקי SMS שונים על ידי יצירת SDK עם בסיס קוד אחיד (go/golang interface).
נכון לכתיבת שורות אלו, יש תמיכה במספר ספקיות ישראליות וזה יתרחב.

 

לצטט ברשת

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

ממשק ציטוטים

מספר אנשים שגם הם חובבי ציטוטים תמיד מקבלים ממני קישור לאוסף עצמו שנמצא בקובץ טקסט במבנה מאוד מוגדר:

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

כל שורה (למעט קישור ווב) לא תעבור את עמודה 80, ותהיה ירידת שורה במידה וכן.

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

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

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

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

כל זה ללא שימוש בשום framework או ספרייה כלשהי, אלא נטו קוד שלי.

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

תהנו 🙂

5 דברים טכניים שלמדתי השנה

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

1. אני עדיין צריך ללמוד יותר VIM

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

אחד הדברים שלמדתי מחדש השנה הוא Text Object. היכולת של vim לזהות דברים למשל פסקאות, שאתה בין סוגריים, מרכאות, מילים וכיוב'.
בעוד שיש בברירת המחדל תמיכה די טובה. יש הרבה תוספים שמוסיפים לזה עוד יכולות, או רוכבים על הקיים ומוסיפים לזה תכונות.
למשל היכולת להבין "מתודה" כפסקה, או class ככזו. ויצא לי בעצם ללמוד מחדש את כל השימוש בה, שדי הדחקתי. אם עד השנה הזו הייתי משתמש המון ב visual mode, השה הזו ירדתי בכמות השימוש במצב זה, בזכות ה Text Objects.

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

2. אני יודע מה מפריע לי כל כך בלכתוב טסטים

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

אתחיל מכך שאני כן חושב שבדיקות הן חשובות, אבל לא בגישה העיקרית ששולטת בשוק.
למשל אם תלכו לחבילת go שיצרתי לאחרונה בשם gostrutils, תגלו כי יש לי שם בד"כ קרוב ל100% cover לפונקציות שם.
זה מאוד חשוב לי שיהיה 100%. עד כמה שזה נשמע מוזר, אבל מה זה אומר בעצם 100%?!

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

  • unit test – סט בדיקות שפונקציות עובדות כראוי – בד"כ זה אומר מעט קוד שבודק חלק קטן מתוך פונקציה.
  • בדיקות פונקציונאליות – מוכרת בשם integration testing – כלומר האם לוגיקה קטנה עושה מה שביקשו.
  • בדיקות שימושיות – בד"כ נקרא end to end או E2E בקיצור, אומר כי בודקים האם טופס מסוים מתפקד נכון
  • בדיקת מערכות – האם המערכת הכוללת עובדת ומתפקדת כמו שצריך, כולל תשתיות, ולא רק הקוד
  • בדיקות ביצועים – נקרא גם benchmarks – בדיקה עד כמה קוד, פונקציה או פוקציונאליות מהירה

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

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

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

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

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

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

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

3. אין לי אהבה לקוד

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

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

הניסיון לכתוב את הדבר היפה ביותר בצורה הטובה ביותר עם O(log(n)) במקרה הכי אופטימי, ו O(n) הכי פסימי פשוט לא רלוונטית הרבה פעמים.

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

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

4. לא כל המפתחים חושבים זהה

יצא לי להביט על קוד אשר הבנתי אותו, אבל לא מה ניסו לפתור בו.
השתמשו ב hash map אשר מחזיק 3 דברים בתוכו בסה"כ ומוזן בזמן עליה, והבעיה היא ששינוי התוכן התבצע מתוך מספר טרדים, ואז קיבלנו race condition רנדומאליים וקריסות.

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

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

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

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

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

5. מבחנים לא נכונים

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

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

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

עלילות vim בריבוי שפות

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

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

כמובן שהאדם הזה הוא אני.

כרגע פתרתי את הבעיה, אך בגישה שאינני אוהב אותה.

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

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

אני לא היחיד שעושה את זה. ישנן הפצות שונות של vim, כאשר אחת המפורסמות בהן היא Space-VIM.
היא בנויה להיות גנרית ובעלת יכולת התאמה אישית.

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

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

וכאן מתחילה הבעיה – יש איתו race condition מול תוספים שכן מותקנים אצלי וזו היתה הבעיה.
לגלות את הבעיה לא היה כזה פשוט, היות ויש אצלי למעלה מ140 תוספים שונים (חלקם מכוונים שפה או טכנולוגיה מסוימים) וחלקם כללים יותר, למשל ניהול פרויקטים ב vim, ושימוש בפקודות של vim לשם כך (כן יש גם את זה בvim).
או הוספת תמיכה ל Text Objects (בקרוב פוסטים בנושא) שונים, ובכך אני יכול לכוון את vim שיתמוך בפסקאות על ידי תחביר מסוים במקום הגדרת "טקסט" פשוטה, והרשימה עוד ארוכה.

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

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


כניסה לפרויקט קיים

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

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

התחלה

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

טעויות בכניסה לקוד/פרויקט קיים

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

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

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

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

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

הדרך שלי ללמוד

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

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

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

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

רכיבים קטנים

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

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

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

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

עבור מה השיטה לא מתאימה?

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

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

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

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

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

הגדרות שגויות לnvim

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

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

איזה סופ"ש אחד החלטתי לפתוח קוד Go שלי ולהמשיך ולעבוד עליו.
פתאום קיבלתי המון שגיאות לא קשורות לקוד שלי, אלא לvim-go.
אמנם עברו 3 חודשים מאז שהתחלתי לכתוב את הקוד בזמני הפרטי, אבל מה השתנה?!
הלכתי לפי הודעות השגיאה (בvim/nvim זה ‎:messages), וגיליתי שפונקציה לא קיימת עבור nvim. הלכתי לספריה ש vim-plug מתקין לי תוספים עבור nvim, ועם rg‏ (ripgrep) חיפשתי את הפונקציה ומצאתי אותה. מסתבר שקובץ חדש בשם config.vim לא נטען. אמממ, אבל הוא קיים בספריה.

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

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

set runtimepath+=~/.vim,~/.vim/after
set packpath+=~/.vim
source ~/.vimrc

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

החלטתי לעשות מהלך שעד עכשיו נמנעתי ממנו. לשים את ה"הפצת" vim שלי תחת nvim, ולעשות שינוי ל init.vim שלי. הוא כבר לא מתעניין ב vim בכלל. הכל נעשה דרכו, כולל הצבעה נכונה לספריות שונות.

לאחר ביצוע פעולה כזו, הכל עבד חלק 🙂

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

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

גילוי פורטים פתוחים בצורה נאיבית

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

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

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

  1. התעבורה עברה, אבל לא קיבלנו אישור – הפורט פתוח אבל מפולטר (מלווה ב Connection Refused).
  2. אין פורט פתוח – אולי מפולטר, ואם כן מקבל drop כאילו אין פורט פתוח.

פעולה זו של SYNchronize היא הדרך עבורינו לגלות האם TCP פתוח.

ההצעה הפשוטה ביותר להתמודד עם זה, זה לנסות לפתוח חיבור TCP בפורט המבוקש לכתובת, במידה ונפתח, החיבור פתוח. במידה והחיבור נכשל (אם קיבלנו timeout או connection refused), אנחנו יודעים שהוא לא פתוח. כאשר האחרון אומר כי הוא מפולטר.

זו לא הדרך היחידה לעשות את זה, אבל זו הדרך הפשוטה ביותר.

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

רובי:

#!/usr/bin/env ruby
require 'socket'
def tcp_connect(address, port, timeout: 20)
# making sure we are talking with IP
connected = false
addr = Socket.getaddrinfo(address, nil)
sock_addr = Socket.pack_sockaddr_in(port, addr[0][3])
Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
begin
socket.connect_nonblock(sock_addr)
rescue IO::WaitWritable
if IO.select(nil, [socket], nil, timeout)
begin
# try again, might work
socket.connect_nonblock(sock_addr)
connected = :connected # no other exception, then should be yes
rescue Errno::EISCONN # we have a connection
connected = :connected
rescue Errno::ECONNREFUSED # we are filtered
connected = :filtered
rescue Errno::ETIMEDOUT # timeout
connected = :closed
rescue StandardError # something else :'(
connected = :error
end
else # unable to wait for an answer, but no exception was raised
connected = :error
end
rescue StandardError # ops, something went wrong
connected = false
ensure
socket.close
end
end
connected
end
puts tcp_connect('192.168.97.3', 8823)
view raw scan.rb hosted with ❤ by GitHub

גו:

package main
import (
"fmt"
"net"
"strings"
"time"
)
const (
errUnknown = "Unknown error"
connTimeout = "closed"
connFiltered = "filtered"
resolveError = "Resolve error"
lookupError = "Lookup error"
connSuccess = "open"
)
func tcpConnect(address string, port int, timeout time.Duration) (string, error) {
ipList, err := net.LookupHost(address)
if err != nil {
return lookupError, err
}
if len(ipList) == 0 {
return resolveError, fmt.Errorf("Unable to resolve %s", address)
}
connAddr := fmt.Sprintf("%s:%d", ipList[0], port)
conn, err := net.DialTimeout("tcp", connAddr, timeout)
if err != nil {
sErr := err.Error()
if strings.HasSuffix(sErr, "connection refused") {
return connFiltered, err
}
if strings.HasSuffix(sErr, "i/o timeout") {
return connTimeout, err
}
return errUnknown, err
}
defer conn.Close()
return connSuccess, nil
}
func main() {
addr := "google.com"
port := 443
status, err := tcpConnect(addr, port, time.Duration(3)*time.Second)
fmt.Printf("%s:%d - ", addr, port)
if status == errUnknown {
fmt.Println(err)
} else {
fmt.Println(status)
}
}
view raw scan.go hosted with ❤ by GitHub

מתודולוגיות – חשיבה מחודשת

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

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

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

כיצד זה התחיל?

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

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

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

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

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

שאלה אחת שווה אלפי שורות קוד

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

  • באיזה שלב הוא לא מוצג?
  • האם הוא היה מוצג בעבר?
  • האם הוא צריך להיות מוצג?
  • מתי הוא אמור להיות מוסתר?
  • האם יש שימוש בשדה הזה בכלל?

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

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

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

ספר לי סיפור

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

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

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

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

מתכנת ❤ קוד

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

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

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

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

משחק סכום אפס

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

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

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

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

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

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

למשל קרה במקום שעבדתי בו מצב אשר קראו מתוך מסד נתונים לתוך מערך מאוד גדול את כל הנתונים ואז היו שופכים את המידע לקובץ csv. יום אחד הגיע אותו המידע לגודל של 64 מגה, והbuffer נגמר. ראינו load על השרת והמון שימוש ב swap.

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

אין בעיה. אני סופר 65,000 רשומות ויוצר עוד קובץ וכיוב'. הנה מימוש pagination רק עם קבצים. כאשר יש את כל הקבצים, לוקחים את כל הקבצים ומאגדים אותם ב zip.
כמות הזמן של כל העיבוד הזה של מספר קבצים ואז zip לקחה שניה וחצי, כאשר לפני השינוי המקורי המערכת היתה קורסת אחרי למעלה מעשר דקות של עבודה, בגלל נושא הבאפרים וניהול הזיכרון.

סיכום

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

ואשמח לשמוע את דעתכם בנושא.

על שימוש ב VIM ו OCD

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

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

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

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

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

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

אבל הגישה של vi היא שונה כאן. אם כל משפט נגמר בנקודה, אפשר להתחיל בגישה הבאה:
בואו נלחץ על ‏ 2f.‎ (כלומר הספרה שתיים האות f ואז נקודה) ונגלה כי אנחנו מגיעים לנקודה השלישית הנמצאת בשורה שלנו. עכשיו ניתן להקיש 2w ו… הגענו למיקום הנכון. מכאן נלחץ על cw ונשנה את המילה הרצויה.

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

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

תנו לי לנסות את זה שוב. אולי אעשה את זה כך: ‎ 2‎)‎ ואז על w ואקנח ב cw.  טוב זה פחות הקלדות.
ועכשיו נשאלת השאלה – האם אני עדיין יכול לקצר את התהליך. אמנם במקום 7 תווים ירדתי ל5, עדיין זה הרבה בעיני.

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

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

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

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

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

כיצד אני משווה טכנולוגיות

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

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

כיצד מתחילים?

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

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

נגיד הדגש הוא רינדור מידע סטטי שלא משתנה. או אולי זה משהו שיש לו observable, אשר מגלה שינוי במידע.
כל גישה כזו ממקדת אותך בדברים שונים.
למשל React יוצרת מצב סטטי תמידי, וכל שינוי מידע יוצר דינדור מחדש של כל הסביבה אשר מחזיקה במידע.
כמובן שיש מספר דרכים להתמודד שזה לא יקרה (למשל states).
לעומת זאת, יש את Vue.js שזו מחזיקה observable וכאשר מידע משתנה, רק מי שמשתמש במידע מרונדר מחדש על ידי שימוש ב shadow DOM, כאשר שאר המסך לא משתנה או מרונדר מחדש.

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

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

ניסויים

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

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

עד כמה המידע שיש עדכני ומסייע לי להתעדכן?

אם אחזור לדגומא של React מול Vue.js. אז התיעוד של React מאוד מתפזר בתהחלה, ומנסה להסביר concept, בעוד שהתיעוד של Vue מאוד ממוקד מטרה – מה לצפות כאשר מתחילים לעבוד.
שימו לב, הדבר הראשון ש React עושים זה להציג Hello World. אנחנו לא מבינים כלום עוד, וכבר יש סוג של Tutorial כתיעוד, בלי קשר ל Tutorial.

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

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

אב טיפוס

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

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

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

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

דגשים שגויים

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

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

אבל מה זה משנה אם יש 10,000 כוכבים או 100,000,000 כוכבים בgithub?

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

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

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

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

עוד דגש בעייתי הוא "חברה X עובדת עם זה, אז זה חייב להיות טוב".
ובכן, זה טוב עבורם! לא בהכרח עבורכם.
תראו למשל את Gitlab, הם הסבירו מעבר שהם עשו לVue.js. אבל הם לא נשארו שם.
כלומר הם לא נשארו באמירה "הנה עברנו טבנולוגיה", אלא הם המשיכו לבדוק. ואפילו פרסמו ניתוח שנה אחרי, מה גורל המעבר.

סיכום

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

מחשבות על Github ומיקרוסופט

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

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

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

  1. מלחמה טובה לעסקים.
  2. שלום טוב לעסקים

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

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

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

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

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

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

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

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

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

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

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

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

התמודדות עם שגיאות תוכנה

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

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

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

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

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


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

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

פיתון למתכנת רובי

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

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

אז מה לא אינטואיטיבי עבורי?  הכל 🙂
סתם, הנה הדגמה קטנה לרובי:

להמשיך לקרוא

vim, neovim ו javascript

אני נחשב למתכנת מסוג full stack מסתבר, ככה לפחות הגדירו אותי אחרים.
אני מוצא את עצמי נמצא רוב הזמן כאשר אני מתכנת לפחות, משתמש בvim, כל עוד לפחות, לא מדובר בשפת Javascript, על שלל ספריותיה, כדוגמת React או Vue אשר איתן אני עובד לרוב.

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

כאשר אני מתכנת בשפות כדוגמת Golang, רובי או פיתון, יש לי כלים ממש איכותיים לנושא עבור vim, גם כאשר אני נוגע למשל בתסריטי teraform, אני עדיין מקבל תמיכה יחסית טובה, אבל כאשר מדובר ב JS, זו כבר בעיה. אני חושב שהסיבות הן שיש הרבה תקנים שהם עדיין במצב של offer ו draft וככה es6 וכיוב', יוצרים אתגרים מעניינים.

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

לאחרונה אפילו עברתי ל neovim, אשר דווקא עושה עבודה מדהימה, עם כמה בעיות ממש קטנות, הוא מספק לי כלי מעט טוב יותר מvim עצמו, הוא אפילו מכיל כמה כלים שלפעמים נדמה ש tmux קצת מיותר (למרות שאני עדיין לא משתמש בו), למשל היכולת לקבל מסוף מובנה עם ‎:terminal אשר עדיין מקבל תוכנות של vim בתוכו.

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

 

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

ניתוח מחרוזות חלק א' התאוריה (על רגל אחת)

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

על פניו, זה נשמע די קל ופשוט, אני מוצא משהו המוגדר כ"גבול", ואז אני מחפש על זה משמעות נכון? ובכן, מסתבר שזה לא כזה פשוט. יותר מזה, גיליתי שישנם 2 סוגים של "שפות" כאשר רוצים לנתח מידע – אחת נקראת "שפה דטרמיניסטית" והשנייה "שפה לא דטרמיניסטית". באנגלית הם קיבלו את השמות Deterministic language ו Nondeterministic . כאשר מדברים עליהם, מדברים על הבנת ההקשר (context). להמשיך לקרוא

מתכנת IT

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

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

עכשיו אשאל אתכם שאלה: האם איש DBA שייך לקבוצה הזו? התשובה היא כן. האם הוא איש DevOps? התשובה היא לא.

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

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

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

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

להמשיך לקרוא

הבנת תבניות זמן בשפת Go

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

כאשר מדובר בשפת Go (או Golang למחפשים), הגישה בנויה מעט שונה. במקום place holder רגיל שבו "m" מייצג חודש בעל תו בודד (כלומר אם החודש הוא "5", אז הוא יופיע כ"5", אך אם החודש הוא "10", הוא יופיע כ"10") או "mm" שהוא חודש דו ספרתי (כלומר אם החודש הוא "5", אז הוא יופיע כ "05", וכאשר מדובר בחודש שהמספר שלו הוא "10", הוא עדיין יופיע כ"10") אינה מתקיימת.

התבניות האלו מוחלפות בגישה אחרת, שאותי לפחות מאוד בלבלה במשך הרבה מאוד זמן. הגישה אומרת כי יש לנו offset holders. מה הכוונה? ובכן תאריך ושעה בGo נשמרים בברירת המחדל כמספר שלם בגודל 64 ביט (כלומר int64). בנוסף, ישנו מספר בגודל 32 ביט (int32) שמחזיק בנונו השניות לשנייה מסוימת., ובנוסף לזה, יש גם מערכת לשמור מיקום.
כל המשתנים האלו מאוגדים ברשומה בשם Time.

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

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

בשביל המבנה, יצרו בגו סוג של Fixed Date שהוא המייצג את הימדע הזה:

Mon Jan 2 15:04:05 MST 2006

המידע הזה הוא נקודה קבועה היודעת להיות מתורגמת ל Unix Epoch 1136239445.
להמשיך לקרוא

יצירה וטעינה של ספרייה משותפת בגו

גו היא שפת תכנות מהודרת, אשר יוצרת קבצי ריצה טבעיים של מערכת ההפעלה. מאז גרסה 1.5, נכנסה האפשרות ליצור ספרייה משותפת דינמית (DSO). בגרסה 1.5, התמיכה הייתה ניסיונית, ובגרסה של 1.6, התמיכה נכנסה כתמיכה רשמית של המהדר.

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

כיצד יוצרים ספרייה משותפת בגו? להמשיך לקרוא

מימוש RDP טבעי בשפת רובי

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

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

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

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

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

כתובת הפרויקט: https://github.com/Safe-T/rdp-rb

דברים מציקים בגו

לאחרונה אני כותב הרבה ב golang מספר actors או workers (אם תרצו) שעושים מספר דברים בצורה מאוד גמישה מצד אחד, ומצד שני מאוד מהירה.
השימוש שלי הוא בכך שאני יכול להריץ הרבה instance של אותו הדבר (בין אם זו אותה מכונה או מכונה נפרדת), לקבל אירועים מ AMQP ולבצע משימה.
עד כאן הכל יפה ונחמד.

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

הסיבה לכך די ברורה, אבל שגויה ביסוד שלה – הם מניחים כי אם אתה שומר slice של בתים, אתה כנראה מנסה לשמור מידע בינארי או שונה ממה שצריך.
רצית רק לשמור סדרה של ערכים, שאף אחד מהם לא יהיה גדול מ255? לא, זה מידע אחר.

אם ההתנהגות הזו היתה זהה למשל ל uint16, אז הייתי אומר "אוקי, כל slice שהוא מספרי, ייחשב כמידע". אבל כאן רק אם זה byte, או uint8 אשר הם למעשה זהים אני אקבל המרה ל base64.

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

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

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

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

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

וזה כרגע הדבר הכי מעצבן שמצאתי ב golang. זה לא מונע ממני להמשיך לעבוד, אבל זה בעיני מאוד מעצבן.

פיתון מול רובי, לא מה שחשבתם

הקדמה

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

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

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

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

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

שאלה ראשונה

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

a = 10.times.select{ |x| x % 2 == 0 }
a
=&gt; [0, 2, 4, 6, 8]

זהו, זה כל מה שצריך, שורה אחת 🙂
אסביר אותה בקצרה. ברובי כל דבר הוא אובייקט, אין כזה דבר משהו שהוא "פרימיטיבי" (בהשוואה לג'אווה למשל).
מספר שלם "רגיל", הוא אובייקט מסוג Fixnum. יש לו מתודה אשר מקבלת ירושה ממחלקת האב – Integer בשם times, אשר מבצעת כמות הפעמים של המספר המבוקש איטרציה.
במידה ולא יוצרים block או proc בשביל לרוץ כמניית האיטרציה, חוזרת לנו מחלקה מסוג Enumerator אשר היא מחלקת בסיס לדברים בעלי יכולת מנייה (כדוגמת מערכים). במחלקת ה Enumerator ישנה מתודה בשם select, המאפשרת להגדיר אילו ערכים באיטרציה כלשהי יחזרו אלי כמערך, כאשר אני יצרתי כאן למעשה proc עם הוראות.
היות וברובי השורה האחרונה בהכרח תמיד חוזרת (אם אין explicit return), אז הוא יודע שכאשר יש מספר זוגי, אז אני רוצה שזה הערך אשר יחזור אלי.
עכשיו הנה משהו מעניין על רובי תוך כדי ההסבר – אין ברובי אופרטורים. כל מה שנראה כאופרטור (כדוגמת חילוק השארית ופעולת השוויון), הם למעשה מתודות, ולכן מתודה כדוגמת even?‎ זהה למה שהייתי צריך, ויתר מזה, אין צורך בסוגריים, ולכן זה יכול גם להראות כך:

a = 10.times.select{ |x| x.even? }
a
=&gt; [0, 2, 4, 6, 8]

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

שאלה ראשונה – תת שאלה

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

a = []
10.times do |i|
  a.push(i) if i.even?
  puts a.inspect
end

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

ובנוסף, לרובי גם יש תמיכה בשני תחבירים של משפט טרינארי אשר שם נפל השואל:

i = 1
a = if i.even? then 't' else 'f' end
b = i.even? ? 't' : 'f'

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

c = unless i.even? then 'f' else 't' end

שאלה שניה

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

a = [1, 2, ['a', 'b', [0xa, 0xb] ]]
=&gt; [1, 2, ["a", "b", [10, 11]]]
a.flatten
=&gt; [1, 2, "a", "b", 10, 11]

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

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

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

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

שאלה שלישית

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

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

str1 = 'yoyo'
print 'sdfsdfsfsdfsfsdf' if str1.eql?('aaaaa') || str1 == 'yopo' || str1 === 'yoyo'

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

שאלה רביעית

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

require 'open-uri'

image = open('https://www.google.com/images/srpr/logo11w.png')
open('/tmp/image.png', 'wb+') { |a| a.write(image.read) }
image.close

קצת פירקתי את הדברים לקריאות.
אני טוען את הספרייה open-uri, אשר תפקידה הוא לדעת לפתוח קישורים של ftp, http ו https כאילו היו file descriptor של רובי.

אני במקרה הזה ניגש לקובץ תמונה של גוגל, ושומר אותו כקובץ בינארי בספריית /tmp/ .
שימו לב כי אינני סוגר את הקובץ תמונה שאני שומר בדיסק, והוא נסגר לבד, בזכות פעולת ה proc, אשר בעצם מתבצעת "מאחורי הקלעים" באמצעות yield. וכאשר פקודה זו מסתיימת, הפונקציה של open סוגרת את עצמה לבד.

סיכום

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

ניסויים בתורי הודעות

יש מערכת שאחרי load test הבנתי כי אני חייב לבזר את המערכות שבה. אז התחלתי לבצע מחקר על סוגים שונים של message queue בשביל לבזר את המערכת. עשיתי ניסויים עם המון מערכות שונות, כולל התייעצות עם אנשים שממשים כבר מערכות המשתמשות בהם, והגעתי להבנה כי כנראה ש RabbitMQ מתאים לצרכים שלי.

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

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

למשל אני עשיתי 2 הגדרות בלבד לRabbit להתאים למכונת הפיתוח החלשה שלי, והגעתי ללמעלה מ7 מליון בקשות בשניה אחת, עד שרוחב הפס נחנק, אבל ה load היה על 0.02 לאורך זמן רב. כלומר לא הגעתי ל load שהם דיברו עליו. אבל יכולתי לבדוק לאורך זמן, רק כאשר ירדתי בערך ל5 מליון בקשות בשניה, ואז הרוחב פס כבר לא נחנק לי.

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

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

כך שלי חסר מאוד מידע איך ומה בוצע בפוסט, אבל אני לא מצליח לשחזר את מה שהם כן מזכירים.
העניין הוא, שאני לא בוטח ברדיס לפעולות ה mq שאני צריך, כי אני צריך מחסנית יציבה של worker..
אם חלילה וחס הוא נפל, אסור שהמידע יאבד, ויותר מזה, במידה ואני צריך להוסיף workers, אני צריך מצב של ביזור המידע, בלי שworker אחד ידרוך על השני, ואני מקבל את זה בצורה טבעית ב Rabbit, (ועוד מספר סוגים של MQ שבדקתי), אבל רדיס מעולם לא תוכנן לזה. הוא נמצא בזיכרון, אני כמובן שיכול להכריח אותו לכתוב לדיסק (איטי מאוד, אבל זה מה שקורה עם persistence queue בRabbit), אבל בשביל לעשות תורים בגישה של Round Robin, ובכן, בלי לכתוב משהו ב lua, אני לא מכיר תמיכה לזה ברדיס, למרות שאשמח לגלות שיש.

ככול שאני עובד כרגע (אמנם עדיין בפיתוח) עם MQ, ככה אני אוהב יותר את הרעיון של השימוש ב Rabbit.
במערכת שאני בונה, יש לי מערכת חיצונית שהיא של ספק אחר שמדברת עם HorentQ, דרך הפרוטוקול שנקרא STOMP (בניגוד לRabbit שברירת המחדל שלו היא AMQP אך תומך גם ב STOMP), והיתרונות של מערכת כזו שהיא robust מאוד, מאפשרת להתמודד עם בעיות שונות, כדוגמת קריסת מערכת, ניתוק התקשורת, התנהלות של load balance של מספר שרתים וכיוב'…

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

 

 

שבוע עם Atom

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

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

העורך משתמש בחלקים של כרומיום, ב node.js, ומשתמש גם ב coffeescript עבור התצוגה של דברים (הנעשים בcss).

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

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

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

  • היכולת להגדיר מה הוא API תקין מבחינת תוספות ולא לאפשר תוספות שאינן כאלו
  • היכולת להגדיר תוספים על בסיס מה שאני עובד כרגע
  • קבלת תוספים איכותיים מבית github לשפות שונות, כדוגמת PHP, רובי וכיוב', מעבר למה שמגיע כברירת מחדל.

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

recursive tail

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

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

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

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

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

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

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

מה הכוונה? אשתמש בקוד רובי (שהוא לדעתי קריא מאוד) לשם כך.

רקורסיה בגישה ה"רגילה" תהיה כתובה כך:

def recursive(n)
  if n <= 1
    1
   else
     n * recursive(n - 1)
   end
end

recursive(4)
=> 24

רקורסיית זנב, תראה כך:

def tail_recursive(current, n, result)
  return result if current <= n

  new_result = result * current
  tail_recursive(current + 1, n, new_result)
end

tail_recursive(1, 4, 1)
=> 24

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

אבל לא הכל ורוד בזה. קשה יותר לדבג בעיות ברקורסיית זנב, היות ולמעשה אין לנו stack frames רבים, שיהיה ניתן להבין באיזה שלב יש בעיה. ובכך אין לנו stack trace שיסייע לנו בנושא.

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

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

חשיבה מופשטת

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

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

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

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

תיאור זה מה שhtml עושה מצויין. למשל יש את כל הנושא של aria, המתארת איך להסביר מה בעצם לבצע עם הטאגים, המידע והאלמנטים בכלל עבור קוראי מסך (למשל), וכך למשל הטאג של role מסביר מתוך aria שה ul שיש לנו, הוא בעצם תפריט ולא bullet point, כי לעיצוב אין משמעות, וה div ההוא זה בעצם חלון דיאלוג, וביחד עם שאר ה aria, אני יכול להגיד כי ברירת המחדל שלו, הוא מוסתר.

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

אבל זה לא הכל. גם לעיצוב בhtml יש תיאור, למשל בשביל להגיד שcss הוא עבור המסך. זה נקרא media types.

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

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

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

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

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

מה זה מאפשר לנו להשיג אם זה בhtml (ולא, זה לא דומה אפילו לתכנות)?
דבר ראשון, זה אומר שאנחנו טוענים פחות מידע. למשל יש עכשיו את פרוטוקול HTTP/2. הפרטוקול אומר כי בגלל שיש המון מידע שצריך להטען, במקום לחכות לכל המידע הזה, אני אכניס הכל בבקשה אחת ואצור multiplexing.
טעינה של המון לוגיקה ב Javascript, טעינה של המון CSS שלא לדבר על תוכן שאולי רק לא יוצג ב HTML, כולם יוצרים בעיות ביצועים. אך במידה ואנחנו נוכל לטעון רק את המידע הרלוונטי כאשר הוא רלוונטי בלבד, אז למעשה כמות המידע ירד, ולמרות ש HTTP/2 הוא נחמד, עדיין אשיג אופטימיזציה מצויינת רק מעצם זה שאני לא טוען מידע שאני לא זקוק לו.

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

<div src="/foo.html" alt="no content" media="(max-wdith: 640px and max-height: 480px)">

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

הנה הדגמה גם לזה:

<link href="/styles/bootstrap.min.css" rel="stylesheet" provides="bootstrap" ver="3.3.4">

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

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

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

מעניין איך תפתרו את ה"תרגיל" למעלה …

הבעיות שיש לי עם תכנות ריספונסיבי

הקדמה

אנחנו חיים בעולם שיש בו המון מילות באזז, כדוגמת big data, responsive design, cloud computing, סייבר וכיוב' …
כל המילים האלו מגיעות מעולם השיווק, אבל לאדם טכני לא באמת אומרות הרבה.

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

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

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

מונחי יסוד

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

CSS – הם קבוצה של חוקים אשר יכולים לכסות אחד את השני (מכאן השם: Cascading Style Sheet) בצורה שתספק עיצוב למסמך.

הסבר

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

בטח תשאלו מה ההבדל בין הדברים.
ובכן, כאשר מדברים על עיצוב, אנחנו מדברים על איך המראה יהיה תחת רזולוציות שונות, אבל למשל תקשורת סלולרית (HSPA) כדוגמת 3G, 4G, LTE וכיוב', אינן נלקחות בחשבון, היות והן מדברות על תעבורה, לא על מראה.

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

כך שאולי יש מסמך איכותי עם המון מידע, שמעוצב מדהים ויודע להיות מוצג בהרבה רזולוציות, אבל האם הוא באמת יודע להגיב כמו שצריך למגבלות המכשיר אשר המסמך מוצג בו?

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

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

הרעיון של תכנון מערכות גמישות – חלק ראשון

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

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

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

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

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

כך גם הגישה של actor model שבה אנחנו מקבלים משהו שיודע לשלח בקשות ולקבל תשובות, אך זה לא חייב להיות אותו החלק.

גישה נוספת אשר מפשטת מאוד פיתוח, אך קשה יותר לתכנון נקראת micro services.
הרעיון הוא די פשוט ואנחנו מכירים אותו למעשה מעולם היוניקס: יש משהו קטן שיודע לעשות פעולה אחת ורק אותה, ולהחזיר אותה הלאה.
למשל מכירים את הפקודה cat? היא יודעת להציג פלט מתוך "קובץ" – בברירת המחדל ל sdout.
אם נשלב אותה עם sort למשל, נוכל עכשיו לסדר שורות לפי סדר מסויים שנבחר. אם נשלב עוד תוכנה בשם uniq, עכשיו נעיף כל מה שחוזר על עצמו, ונקנח בפקודה tr, אשר תתרגם לנו סוף שורה לפסיק, ועכשיו נשים את הכל בקובץ חדש, והנה יצרנו מערכת יחסית מסובכת המשתמשת בפעולות מאוד מאוד פשוטות.

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

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

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

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

בחלק הבא, אכנס קצת יותר בצורה טכנית וקצת פחות תיאורטית.

פונקציה מונדית

אחד הדברים שאני שומע מהרבה אנשים אשר מתעסקים בתכנות פונקציונאלי הוא שפונקציה מונדית (monad function) היא בערך התשובה לכל מה ש42 לא מצליח לכסות.

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

הנה קוד ג'אווה סקריפט (מבוסס jQuery) קצר שאסביר אותו עוד מעט:

$('#id').html('<p>success</p>').addClass('success')

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

סביר להניח כי מי שמתכנת עם jQuery נתקל בגישה הזו, אבל מה בעצם מתרחש כאן?

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

function init() {
  var self = this;
  return {
     callMe : function() {
       return self;
     }
  };
}

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

הרעיון לשימוש בקוד יהיה בגישה הבאה:

init().callMe().someOtherMethod() ...

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

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

אנשי DBA

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

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

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

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

למשל בכל מסדי הנתונים שאני מכיר, יש בעיה עם Foreign keys. הם נשמעים טוב, אבל מוסיפים סיבוכיות בכך שהם בעצם מתורגמים לעוד פעולת select. את בעיית הביצועים הזו גיליתי לבד, את הסיבה לכך, ובכן איש DBA הסביר לי. אבל הוא באמת מבין המון דברים. הפתרון לכך פשוט, אבל זה לא נושא הפוסט 🙂

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

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

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

סינטרה מודולרית

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

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

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

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

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

אנחנו משתמשים בRack בעצם, היות וכמעט וכל הframeworks עבור בניית מערכות web ברובי משתמשים בו, אנו זקוקים לקובץ קבוע בשם config.ru.

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

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

לאחר טעינת התלויות, אני טוען קובץ בודד בשם app.rb שהוא בעצם מה שמנהל את האפליקציה שלי. אך במקום להשתמש ב require "רגיל", אני משתמש בפונקציה בשם require_relative, אשר מאפשרת לטעון דברים מהמיקום הנוכחי של הקובץ המנסה לטעון אותה.

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

למערכת שיצרתי ישנם שני מצבים – מערכת לדיבוג REST, ומערכת "בדיקות" אשר עליה אפשר לבדוק שדברים עובדים, והיא בעיקר על תקן "echo" כלשהו.

את המערכת של יצירת המסכים, ושליחת ה REST, יצרתי בקובץ rest.rb, והכל מאוגד שם.
יש שם משהו שהולך לעבור למקום אחר בקרוב, וזה מספר מתודות לסיוע בפעולות.

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

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

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

במקרה הזה, הגדרתי כי במצב של ‎:development משתמשים ב Sinatra::Reloader, אשר מגיע עם Sinatra-Contrib – תת פרוייקט המספק הרבה כלי עזר לדברים שונים.
הסיבה לשימוש ב Reloader הוא לא לאתחל את השרת בכל שינוי שעושים למחלקה של סינטרה, כאשר Reloader מגלה כי התוכן של הקובץ השתנה, הוא גורם ל rack לטעון אותו שוב, וככה אנחנו לא זקוקים לטעינה מחודשת של השרת עצמו.

המערכת שכתבתי, משתמשת ב template בשם haml, למעשה פעם ראשונה אשר אני משתמש בה מרצון. תוכלו למצוא את ה layout.haml שהוא המסגרת הרגילה וכן כרגע קובץ בשם index.haml תחת ספריית view.
ועבור העיצוב, אני משתמש ב Foundation 5, אשר אני אוהב אותה יותר מאשר bootstrap.
עבור Javascript יש גם את jQuery וגם את knockout.js, כאשר אני נעזר גם ב lodash.js למספר דברים פשוטים, והיא מספקת בעצם גרסה שעברה אופטימיזציה ל underscore.

את הקבצים של Foundation, וכל ה Javascript ניתן למצוא תחת public.

דבר אחרון שנשאר לספר עליו הוא שאני משתמש במשהו אשר נקרא puma.
מה זה ?
puma הוא משהו שלוקח את rack וגורם לו להיות שרת לכל דבר ועניין, אשר ניתן לבצע עליו חיבור לשרתי HTTP שונים, כדוגמץ apache או nginx.
החיבור נעשה על ידי הגדרת proxy בשרתים.

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

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

מזלגות נעוצים

בתקופה האחרונה, נעשו מספר פעולות fork לפרויקטים מוכרים, בהם node.js ודביאן.
בנוסף, פרויקט docker מקבל מתחרה, לאחר שחברת CoreOS הודיעה כי לא מקובל עליה הכיון של docker.

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

אתחיל בדביאן
יש המון טינה על systemd, ובעקבות כל הנושא, עם כל הזעם, הוחלט ליצור fork בשם devuan, אשר ימשיך לספק יכולות של sysv init במקום systemd ה"נורא".

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

המאבקים המפגרים האלו, האם להשתמש בsystemd או לא, פוגעים בלינוקס, ולא האם systemd טוב או לא.

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

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

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

Rocket
במידה ולא שמעתם על docker, אתם כנראה ישנים איפשהו, או שרחוקים מעולם ה IT. זה השם החם ביותר מאז שסייבר ומחשוב ענן נכנס לעולם, או אולי זה היה 42 ?

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

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

החברה של CoreOS, רוצים container ומשהו שזו כל ההתמחות שלו, אבל docker מתחיל להיות stack שלם של דברים, והם ממש לא אוהבים את זה, כי בסופו של דבר,זו סוג של תחרות, לא ?

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

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

Firebird 3.0

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

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

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

  • DSQL
  • ESQL
  • PSQL

DSQL – הם בעצם השאילתות שכותבים ומריצים בצורה דינאמית באמצעות API. המקדם D מייצג את המילה Dynamic.
ESQL – הם בעצם שאילתות עם preprocessor, כאלו שכותבים למשל דרך תוכנה שלנו. ההבדל בינה לבין DSQL היא שבשפה זו משתמשים בפקודה EXEC. הפירוש של המקדם E מייצג את המילה Embedded.
PSQL – השפה שבה כותבים stored procedure וטריגרים. המקדם של P מייצג את המילה Procedural.

בנוסף ישנה שפה בשם DDL – ‏Data Definition Language. זו השפה בה עושים פעולות כדוגמת create table או create database.

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

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

SQL – כלל השפות:

  • Merge Syntax
  • פונקציות window
  • תמיכה ב regex בביצוע SUBSTRING
  • נכנס סוג נתונים בשם Boolean
  • יכולת לשמור גרסאות עם RDB$RECORD_VERSION
  • cursor יציב

PSQL:

  • פונקציות SQL
  • פרוצדורות
  • טריגרים, פונקציות ופרוצדורות משפות חיצוניות
  • חבילות
  • חריגות עם פרמטרים
  • SQLSTAT במצב של WHEN
  • שימוש ב continue בתוך לולאות.
  • cursor יכול להחזיר התייחסות כמשתנה מסוג רשומה
  • cursor דו כיווני

DDL‏:

  • תמיכה במצב null של עמודה ו domain
  • היכולת לשנות קידוד ברירת המחדל של מסד הנתונים
  • הוספה של Identity Column – המקביל ל serial בPG ו Auto Increment של SQLITE ו MySQL
  • תחביר עבור RECREATE SEQUENCE/GENERATOR
  • טריגרים עבור DDL
  • תמיכה ב DATABASE LINGER

אבטחה:

  • תמיכה database encryption
  • תמיכה מורחבת יותר בהרשאות Object
  • הרשאות לעבודה עם DDL
  • הרשאות ברמת מסד הנתונים
  • תוספות לעבודה עם Roles

פיקוח:

  • הוספת יכולות נוספות לסטטיסטיקה אודות שאילתות ומסד הנתונים בכלל

אז מה זה כל הסעיפים האלו ? להמשיך לקרוא

יחידות בדיקה, כן, לא, אולי

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

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

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

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

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

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

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

יחידות בדיקה (מיד יצעקו הבודקים), לא נועדו לכך, ואני אומר להם כי הם טועים !

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

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

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

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

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

האם בדקת באמצעות ‎ /dev/full האם המערכת שלך יודעת להתמודד עם מצב בו אין מקום יותר לשמור מידע ?
רק השבוע סידרתי את MySQL שהרס טבלה כי מערכת הקבצים הגיעה ל 100% ולא היה מקום לרשום את הרשומה, אז הטבלה נהרסה.

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

יותר מזה, אתה בודק את הקוד שלך על 10,000 רשומות, הלקוח קצה שלך אבל משתמש ב10,500 רשומות, ואז יש איטיות (ב10,499 עדיין אין), איך לא ידעת להוסיף עוד רק 500 רשומות לבדיקה ? זה מה שמנהל מסוים יצעק עליך, לא ?

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

האם בכלל יש פתרון למקרי קצה כאלו ?

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

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

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

אז עכשיו חלק מהקוראים כאן (שעוד נשארו ולא עזבו בזעם או שיעמום) יגידו "כן, אבל תראה את סוג הבעיות ב X מול סוג הבעיות של Y".

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

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

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

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

מוגש כחומר למחשבה.

knockout.js

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

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

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

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

אז החלטתי לפנות לכלים כדוגמת Backbone, Angular.js ו Ember.js. אבל גיליתי כי עבור הצרכים שלי הן מפלצות – ביחוד אנגולר ואמבר. הכלי שניסיתי היה ד"א אנגולר.

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

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

הכלי נקרא knockout.js. הכלי הזה בנוי בגישה של "תשאיר לי את ה data binding, השאר שלך לעשות מה שתרצה".

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

הגישה של konockout.js קיבלה את השם Model-View-ViewModel אשר מאוד מוכרת לי מעולם הדלפי, רק בצורה שונה.

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

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

זה נעשה בצורה די פשוטה בג'אווהסקריפט נקי:

function observable() {
   if (arguments.length > 0) {
      //write stuff
   } else {
      // read stuff
   }
}

למעשה arguments זה המקביל ל var_args של C או open array של פסקל, רק שהוא מכיל בתוכו אוביקטים כי זו שפת javascript.

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

הנה קוד מאוד פשוט ב knockout:

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
function ViewModel() {
    this.firstName = ko.observable("Joe");
    this.lastName = ko.observable("Bloggs");
 
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}
 
ko.applyBindings(new ViewModel());

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

זהו, זה כל מה שהיה צריך. אין צורך בשיריון של namespace, איזורים וכיוב' … בלי לבחור סוג של template וכיוב'.
במקום שבו משתמשים בשדות, אז נכנסים לתוך ה namespace של אותו אובייקט, ואז במידה ורוצים לצאת החוצה משתמשים ב ‎$parent ובמידה ורוצים להתחיל מההתחלה, משתמשים ב ‎$root כלומר בנקודה שבה קראנו ל applyBindings.
אם אתם רוצים את האובייקט עצמו שאנחנו משתמשים בשדה מסוים (למשל כאשר משתמשים במערך), משתמשים ב‎$data וישנם עוד סוגי משתנים. הנה הסבר פשוט בנושא.

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

go_libhdate

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

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

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

אם תריצו ידנית את הפקודה

go tool cgo

על חבילה המדברת (כלומר עושה binding) עם C, תראו שתקבלו בעצם ספרייה בשם ‎_obj ושם כל ה"קסם" גלוי לעיניים: להמשיך לקרוא