קטגוריה: go

הדגמה ליצירת קובץ 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 כאילו היה קורונה, בעייתי מידי ויכולה לסבך את תחזוקת הקוד ואף להוסיף עוד כמה קריאות מיותרות במערכת. ולא להזכיר כי אולי זה לא יהיה קריא, ויש להשתמש בגישה הזו רק כאשר היא נכונה, ולא כמשהו דתי שיש להימנע.

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

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

אני עובד עם 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).
נכון לכתיבת שורות אלו, יש תמיכה במספר ספקיות ישראליות וזה יתרחב.

 

עלילות 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.


הגדרות שגויות ל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. שניהם מסוגלים לחיות זה לצד זה בהפצה שלי עכשיו.

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

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

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

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

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

להמשיך לקרוא

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, אבל כבר פחות ופחות אני מוצא את עצמי עם צורך לשנות אותם. אשמח לקבל פידבק מאחרים לגבי העובדה בשביל שאוכל לשפר ולהשתפר, וכמובן שכולם מוזמנים לעבוד עם ההגדרות שלי.

הבנת תבניות זמן בשפת 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 והגישה אליהן מתרחשת על ידי כתובות זיכרון.
במידה ופרוסס מסויים מקשר אליהן בצורה סטטית, כלומר בזמן הידור, יודעים את המיקום שלו ביחס לשאר הפונקציות של הפרוסס, כאילו היה חלק מהפרוסס עצמו.
כאשר הטעינה מתבצעת בזמן ריצה, יש אתחול זיכרון דינאמי אשר מחזיק את הכתובת.

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

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

לאחרונה אני כותב הרבה ב 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. זה לא מונע ממני להמשיך לעבוד, אבל זה בעיני מאוד מעצבן.

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

יש מערכת שאחרי 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 זו הגישה המתאימה לי, ובמידה ולא, תמיד אפשר לעבור בלי להחליף קוד 🙂

 

 

go_libhdate

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

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

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

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

go tool cgo

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

גו – השלמתה של שפה

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

למשל יכול להיות שרת ג'ינטו מ 2007 (שלא התעדכנה) ומערכת פדורה 20, שהמערכת צריכה לרוץ על שניהם.

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

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

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

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

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

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

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

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

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

עוד מערכת לדיווח על אזעקות

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

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

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

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

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

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

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

 

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

שחרור משאבים ברובי ו Go

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

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

כיצד זה מגיע לידי ביטוי ?
הדגמה בGo:

func open_file {
f, err := os.Open("/tmp/a_file")
if err != nil {
log.Fatal("Unable to open a_file")
}
defer f.Close()
// do something here
}
view raw defer.go hosted with ❤ by GitHub

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

ברובי קוד שכזה יראה כך:


def read_file
  f = open('/tmp/a_file')
  # do something here
rescue => e # catch exceptions globally
   $stderr.puts("Unable to open a_file") if e.kind_of? Errno::ENOENT
ensure
  f.close if f
end

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

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

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

Let's learn go – הרצאה

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

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

את ההרצאה ניתן למצוא כאן. רשימות ההרצאה ניתנות להרודה מכאן. ו"קוד המקור".

My first Go program

I'ved wrote at the Passover holiday a very short script (took almost 5 minutes) to update my vim based plugins that are taken from scm.

So I'ved decided to do the same with the Go language, and make it my first program with it.

It took me a lot of effort to write it, because it was my first program, and learning new syntax, rules, forgetting what I already know in programming etc…

Here is the first version of my code.

I hope later on to write it better, and even in concurrency support -> The reason why I'm learning the programming language.

I wish to thank all the people at the Go community at Google+, they are very helpful and easy with new comers 🙂

Note: Before publishing code, always do "go fmt <file>", so it will be in the way of the gopher 🙂

let's go

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

אז הקדשתי שעה (פלוס) להבין קצת יותר לעומק את השפה.

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

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

הנה משהו אחד כזה – משפט if אשר מאוד בעייתי בעיני:

if a := UserAge(); a < 12 {
 ...
}

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

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

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

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

לחרדת הקורא צפריר, go מהודרת סטטית לelf (בלינוקס) את קבצי הריצה שלה (אלא אם משתמשים ב gccgo), מה שאומר שהם יכולים לרוץ על כל מערכת המתאימה להם. כלומר גם אם קימפלתם אותם על debian stable, תוכלו להריץ אותם על arch ללא צורך למקפל מחדש.
ואם זה לא מספיק להחריד את צפריר, אז יש לשפה גם מנהל חבילות משל עצמה במקום לסמוך על הפצה כזו או אחרת …

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

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