קטגוריה: טיפים וטריקים

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

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

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

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

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

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

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

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

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

רובי:

גו:

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

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

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

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

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

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

סיכום

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

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

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

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

סיכום

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

הבנת תבניות זמן בשפת 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.
להמשיך לקרוא

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 שיסייע לנו בנושא.

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

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

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

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

שינוי פורמט לוג של Rack, והסתרת אזהרות ברובי

יש לי מערכת העובדת עם Sinatra, unicorn ו nginx ביחד.

הבעיה היא, ש nginx נמצא בראש, והוא מעביר את הבקשה ל unicorn, אני מקבל את כתובת ה ip בלוג של ה nginx ולא של הפונה המקורי, ורציתי לשנות את זה, שאראה את הכתובת של הפונה, ולא של nginx.

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

# ... 
# snips 
# ...

# Overriding the original constant
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}

# ... 
# snips 
# ...

def log(env, status, header, began_at)
  now = Time.now
  length = extract_content_length(header)

  logger = @logger || env['rack.errors']
  logger.write FORMAT % [
    # adding IP
    env['HTTP_X_REAL_IP'] || env['HTTP_X_FORWARDED_FOR'] || 
    env["REMOTE_ADDR"] || "-",
    env["REMOTE_USER"] || "-",
    now.strftime("%d/%m/%Y %H:%M:%S"),
    env["REQUEST_METHOD"],
    env["PATH_INFO"],
    env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
    env["HTTP_VERSION"],
    status.to_s[0..3],
    length,
    now - began_at 
  ]
end

# ...
# snips 
# ...

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

proxy_set_header        X-Real-IP       $remote_addr;

במידה והוא אינו קיים, אני מנסה לבדוק האם HTTP_X_FORWARDED_FOR קיים, במידה ולא, אז אנסה את מזלי עם REMOTE_IP, ואם זה גם לא, אז אין IP.

במידה ותריצו את זה, תגלו אבל בעיה חדשה – הודעת אזהרה ש FORMAT כבר הוגדר:

warning: already initialized constant Rack::CommonLogger::FORMAT
/home/ik/.gem/ruby/2.0.0/gems/rack-1.5.2/lib/rack/commonlogger.rb:24: warning: previous definition of FORMAT was here

אז מצאתי להודעה הזו פתרון נחמד ופשוט:

module Kernel
  def suppress_warnings
    original_verbosity = $VERBOSE
    $VERBOSE = nil
    result = yield
    $VERBOSE = original_verbosity
    return result
  end
end

והשימוש בו יהיה בצורה הבאה:

module Rack
  class CommonLogger
    Kernel::suppress_warnings do
      # Overriding the original constant
      FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
    end

    def initialize(app, logger=nil)
...

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

הקוד המלא נמצא כאן.

הפילוסופיה של הקוד

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

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

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

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

כל זה נעשה בשפה העברית.

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

ענב הזעם – API

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

ישנם עוד מספר סוגי framework עבור עולם ה web, בהם Sinatra, שאני משתמש המון, ולאחרונה התחלתי לשחק גם עם framework מאוד מעניין שנקרא grape.

Grape הוא framework אשר נועד לתת לנו כלי לפיתוח API. זה אומר שכל הפיטצ'רים שלו הם על טהרת REST ואין שום דבר הקשור להצגת templates למשל בצד המשתמש.

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

הנה הדגמה קלה כיצד תראה תוכנית שכזו: להמשיך לקרוא

טיפים על עבודה ב ssh

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

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

בתוך הספרייה ניצור קובץ בשם config ונכנס לו את ההגדרה הבאה:

Host *
  ControlPath ~/.ssh/sockets/master-%l-%r@%h:%p
  ControlMaster auto
  GSSAPIAuthentication=no
  ServerAliveInterval 25
  Compression yes
  IdentityFile ~/.ssh/id_rsa

ה"חלק" הזה שיצרנו בעצם יוצר קבוצה של הגדרות עבור 100% מהחיבורים שלנו (אלא אם נדרוס אותן). אנחנו יודעים זאת, בזכות הglob של כוכבית.
אנחנו אומרים לו ליצור קובץ socket על שם החיבור המדויק שלנו, ושopenssl ינהל אותו לבד. מדובר למעשה ב unix socket, וזה מה שמאפשר את השיתוף.
אנחנו אומרים למערכת שלנו כל 25 שניות לשלוח סוג של ping בשביל להשאיר את החיבור פתוח (אחרת יש חיבורים שיסגרו בשרתים שונים אם אין תגובה אחת לזמן מסוים), אנחנו דוחסים את המידע העובר עם החיבור, ובסוף אומרים מה המפתח ברירת המחדל שלנו.

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

הקובץ של config מאפשר לנו גם לבצע הגדרות מדוייקות לשרתים שונים. למשל: להמשיך לקרוא