2018-05-20

הרשו לעצמכם קצת אי-סדר בקוד (דעה)

כשמדברים על סדר ו״ניקיון״ קוד - יש סקאלה של מצבים אפשריים:
  • בקיצוניות אחת: ״בנה ותקן״ בו כותבים את הקוד המיידי ביותר האפשרי בכדי להפעיל את הפיצ׳ר הבא, מגלים באגים ואז הולכים לתקן אותן (וב 20% מהפעמים או יותר יוצרים באג חדש). כל העניין של סדר הוא ״למרובעים״ או ״אנשים בעלי זמן פנוי״.
  • בקיצוניות שניה: פדנטיות קוד, בה כל קבוצת שורות של קוד עוברות refactoring וסידור על בסיס יומי. מקסימום סדר, מקסימום encapsulation, שום גרם מיותר של אי-סדר בקוד.

לכאורה פדנטיות הקוד היא המצב ״הטוב״ ו'בנה ותקן' הוא המצב ״הרע״ ולכן רובנו שואפים לפדנטיות קוד - אבל אף פעם לא מגיעים לשם: מאוד קשה להגיע לשם, ובמיוחד כאשר יש deadlines ו deliveries.

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

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

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

כמו מהירות האור שהיא יעד בלתי-מושג לגוף עם מאסה, כך גם ״סדר קוד מופתי״ הוא יעד בלתי אפשרי לגוף בעל תפיסה-ביקורתית*.

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

--

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


מדע בידיוני, בינתיים.



מדוע בכלל לחתור ל״סדר מופתי״ בקוד?


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

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

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

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

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

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

הבעיה היא שמצב כזה הוא כבר מאוחר מדי - ובד״כ יש צורך בשלב כזה לכתוב את בסיס-הקוד מחדש. באסה!!.


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

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

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

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


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



כוונות מול מציאות



מה עושים?ֿֿֿ


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

האם אני מציע לוותר על ״איכות קוד״ כערך עליון?

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

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


כאשר אנו חותרים לקוד "האיכותי ביותר האפשרי" - סביר שנבזבז אחוז ניכר מהזמן שלנו ב״שיפורי איכות״ חסרי משמעות, או לפחות כמעט חסרי משמעות:
  • הגנה בפני מקרי-קצה תאורטיים, שלעולם לא יתרחשו. 
    • לעתים מדובר בימים של עבודה רק כדי ש״הקוד יהיה מסודר״.
  • הרחבת ה Framework / מבנה הקוד למקרים עתידיים - שלא עומדים להתרחש.
  • ביצוע סידור גדול בקוד - רק על מנת לגלות חודש מאוחר יותר שהוא מגביל / לא נכון - וצריך לסדר מחדש.
  • פתרון באגים שלא מפריעים לאף אחד או לפחות לא יגרמו לשום impact עסקי שניתן למדוד.
  • שיפורי UI חסרי-חשיבות:
    • למשל התאמה של מסך אחד - שהיה עקבי יותר עם מסך אחר, אבל בעצם משתמשים שונים משתמשים בשני המסכים - אז כמעט אף אחד לא ישים לב.
  • שיפור ביצועים - במקומות שלא זקוקים להם. 
    • ראיתי להרבה שיפורי ביצועים בחיים - שלעולם לא היו זקוקים להם, ורבים מהם בכלל לא היה ניתן למדוד מרוב שהם היו זניחים. 



אז מה, בכל זאת, עושים?


טוב. אני מקווה שהצלחתי להעביר את הדילמה.

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

גישה נוספת שאני רוצה להציע היא ניהול דינאמי של מצב איכות הקוד:
  1. ננהל רשימה "חיה" של היבטים לא אופטימליים בקוד. את הרשימה כדאי לאסוף ממגוון חברים בארגון.
    1. אם תנהלו את הרשימה הזו ברצינות - מהר מאוד תגיעו לעשרות פריטים ברשימה, ויותר.
    2. אם לא הגעתם - סימן שלא חיפשתם מספיק טוב. זה כמו לשאול את עצמכם ״במה אני פחות טוב?״ - ולא למצוא שום דבר.
  2. מתוך הרשימה - מצאו פריטים בעלי Impact: עדיף impact עסקי, אבל גם impact טכנולוגי - פחות בעיות, יתר קלות לקוד שנוסף למערכת, בקרה טובה יותר על המערכת וכו׳.
    1. סביר שייקח לכם כמה סיבובים על מנת להבין היכן באמת נמצא ה Impact - רעיונות עלולים להישמע טוב, אך להיות חסרי impact לחלוטין בפועל.
  3. תזמנו זמן עבודה מוגדר לצורך שיפורים יזומים ופתרון הפריטים החשובים ביותר ברשימה. ה benchmark הבריא שאני מכיר הוא כ 20% מזמן העבודה
    1. פרשנויות שונות מה עושים בזמן הזה (כמו: ״באגים הם ברשימה״) - יכולים להפוך אותו ללא רלוונטי.
  4. שווה לעבוד בצעדים קטנים. אם יש בעיה גדולה, הרשו לעצמכם למצמצם אותה ולראות מה ההשפעה העסקית שנובעת מכך. למשל: תהליך ה deploy אורך 15 דקות? נסו לבצע שיפור (נניח: המקצר אותו ל8 דק') ולראות מה ההשפעה בפועל. זה יותר טוב מלהשקיע פי כמה עבודה בכדי להביא אותו ל 2 דקות - ולגלות שה impact לא כ"כ משמעותי.
  5. חשוב לערב את אנשי הצוות בהחלטות ובעשיה. 
    1. זה לא רק עניין של engagement ושותפות-גורל. זה גם עניין של ״חלונות שבורים״: לחדד ולהזכיר לכולם שקוד טוב הוא ערך חשוב - ושאנחנו כן משקיעים בו. ושהשקעה בו נושאת פרי.


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

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

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


הנה כמה פריטים מרשימות כאלו שניהלתי - שאכן עשו impact:
  • פירוק של טבלה גדולה ב DB לכמה טבלאות - ע״פ דפוסי שימוש שונים.
  • העברת נתונים מבסיס הנתונים ל Redis.
  • שינוי הייצוג של הנתונים - למבנה קל יותר לעבודה.
  • פירוק שרת Redis לשני שרתים ע״פ דפוס שימוש: כזה ל cache וכזה לנתונים שחשוב שיישמרו. הפירוק אפשר לנו לנקות את ה caches ביעילות מבלי גרימת נזק.
  • כתיבת מודול או microservice מחדש. צריכות להיות סיבות טובות למדי - לכתיבה מחדש (ופעמים רבות - יש כאלו).
  • שינוי מבני של flow מורכב במערכת.
  • ניקוי קוד ישן / נתונים ישנים מבסיס הנתונים.
  • הוספת כלי ניטור על מצבים חשובים במערכת - שחסרה נראות שלהם (דברים מאוד ספציפיים)
  • Key Security Items
  • Key Performance Items
  • סידור נתונים ל BI
ברור שהיה עדיף למנוע מראש את היווצרות של המצבים הללו. בהינתן הדינמיקה הארגונית / הלחצים / וחוסר ידיעת העתיד - עד כמה זה באמת אפשרי?
ברור שזו רשימה ספציפית בהקשר ספציפי - שלא אוכל להעביר בפוסט.


הנה כמה פריטים שהיו ברשימות שכאלה, ולעולם לא הגיעו לידי מימוש:
  • סידור flows מסוימים בקוד בכדי שיהיו פשוטים וברורים יותר (הרבה מקרים).
  • HTTP being used internally (ולא https)
  • מקרים מסוימים בהם כשל במיקרו-שירות אחד - גורר כשל במיקרו-שירות אחר
  • Schemas שונים בבסיס הנתונים שנמצאים ב encodings שונים: אחד בשוודית והשני ב ISO.
  • ניקוי ה git repository והסרת 25MB מיותרים.
  • Various Security Items
  • Various Performance Items
  • סידור נתונים ל BI
  • וכו׳
האם הפריטים הללו ראויים לרשימה?!
בוודאי שכן!:
  • הם מהווים נקודת reference חשובה - מה יותר חשוב ממה. מה יגרום ליותר impact ממה. את הרשימה נרצה כל הזמן למיין כאשר הפריטים בעלי ה impact הגבוה ביותר נמצאים בראש - ועוברים תדיר לעבודה.
    • נ.ב.: עדיף פריטים קטנים שנכנסים כל הזמן לעבודה - מפריטים גדולים שנכנסים לעבודה רק פעם בחצי-שנה או רבעון. זהו מסר חינוכי ומתמרץ, וגם הזדמנות להכניס יותר שיפורים משמעותיים.
  • עצם מעבר על הרשימה עם הצוות יוצר את ההבנה שאלו דברים לא טובים, שיש להימנע מהם בעתיד. יש פה למידה, ובניית קונצנזוס - דברים חשובים גם כן.
  • עם הזמן, בעיות מחמירות / גדלות בחשיבות - ואז פריטים שהיו בתחתית הרשימה - עוברים לראשה. חשוב לעקוב ולהבין את הבעיות על מנת לאתר אותן ולטפל בהן מספיק מוקדם.
    • בכלל, יצירת בסיס-ידע של בעיות במערכת שקרו ואפשר ללמוד מהן - הוא דבר שימושי. מה שקורה במערכת שלנו והוכח כבעייתי - הוא לקח הרבה יותר חזק ומשמעותי ממקרים תאורטיים, המתוארים בספרות כתובה כלשהי.





סיכום


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

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

אני זוכר את התקופה שעבדתי ב SAP והיינו פותחים Critical Bug (הרמה הגבוהה ביותר) על כל מקרה בקוד בו עשו [3] catch Throwable (מעל ה JVM). היום אני מתעלם ממקרים כאלו - ואפילו כותב catch throwable בעצמי - אם זו הצורה המקובלת לתפוס Exception בבסיס הקוד בו אני עובד.

היה לנו איזה כלי בשם Sonar (ניתוח סטטי של קוד) שניסה לחשב את ה Technical Debt של בסיס-קוד שהוא סרק בצורה מספרית / דולרית. לכל Catch Throwable הוא נתן תג מחיר של  כמה עשרות דולרים (לא זוכר בדיוק). יכולתי לפתוח tickets חסרי impact לחלוטין, ברמת הדחיפות הכי גבוהה - ולהרגיש טוב עם עצמי שאני חוסך כסף לחברה. צעירות!


אז שיהיה בהצלחה!


---



[2] על בסיס מדדים כמו cyclomatic complexity או מספר הפרמטרים המועברים לפונקציה.

[3] למרות שהייתה כוונה ברמת ה JVM יום אחד לעשות שימוש ב Throwable למטרה מעט אחרת - כבר עברו 23 שנה, ולא נעשה בו כל שימוש. כ״כ הרבה מפתחים השתמשו כבר ב Throwable כאילו הוא Exception לכל דבר - כך שכבר לא נראה לי שניתן לייחס לו בגרסה עתידית משמעות אחרת.



2018-04-21

נקסט אינשורנס מסומנת ע"י כלכליסט כמספר 3 בסטאראט-אפים המבטיחים לשנת 2018




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

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


מקור

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

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

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

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

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

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

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

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

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




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

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

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

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

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

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

אז הנה, פרגון לצוות שעושה את זה:



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



2018-04-19

כמה דברים שרציתם לדעת על גיט - אבל חששתם / התעצלתם לשאול

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

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

מתוך הצורך הקיומי לשימוש בגיט, קיימת רמת מיומנות בסיסית הנפוצה בקרב אנשי תוכנה.
נקרא לה "רמה 2".

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

למשל: 
  • עניינים של ה internals - כיצד גיט ממומש, וכיצד הוא עובד מאחורי הקלעים.
  • כל מיני מונחים מוכרים, אבל לא מובנים, למשל: Fast-Forward, ReReRe, Rebase, או Bisect - היא רשימה מייצגת שאני נתקלתי בה, של מונחים שאנשים רבים שמעו - אבל לא יודעים באמת מה הם אומרים.

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

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

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

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



Rebase


rebase הוא דרך חלופית ל merge על מנת למזג עבודה בין branches.
יש הנחה ששמעתי הגורסת שמשתמשי SVN לשעבר, הרגילים לעבור על branch יחיד (להלן "trunk") - נוטים להשתמש ב rebase כי הוא מזכיר את הכלי הקודם שלהם. לא יודע.

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

אותו קוד, אותם שינויים - שיטת מיזוגים שונה (בד"כ ה rebase יהיה ארוך יותר)

Merge - מציג את המצב המסובך כפי שהוא.
Rebase - יוצר מציאות קלה יותר למעקב - אך יש מחירים לייצר אותה.


נניח שיש לנו branch עם 6 commits שאנחנו רוצים למזג ל master.
כאשר אנו משתמשים ב merge - זו פעולה אחת, עם סיכוי מסוים לקונפליקטים. הקונפליקטים נוצרים ע"פ המצב הסופי של ה branch שלי מול המצב הסופי של ה master.

rebase הוא מורכב יותר: הוא ייקח את ה branch שלי, commit אחר commit, ויטמיע אותם בראש ה master branch כאילו רק עכשיו כתבתי אותם. אחד אחרי השני.

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

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

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

נ.ב: בעוד את פקודת git merge מפעילים מתוך branch היעד (אליו רוצים למזג), את פקודת git rebase מפעילים מתוך branch המקור - ה branch אשר את תוכנו רוצים למזג/"להרכיב" על branch אחר.



Fast Forward


אין סיכוי שלא נתקלתם במונח Fast Forward (להלן FF) בעבודה עם גיט.

"טוב, קרה פה משהו מהיר. נראה שאין בעיות. יופי - נמשיך הלאה!" - היא התגובה המקובלת להודעה של גיט שבוצע FF.


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

כאשר יש לי branch (למשל: feature branch) שאני רוצה למזג (למשל: ל master) אבל ה master לא השתנה מאז שהתפצלתי ל feature branch - אפשר לפשט את הדברים.

merge בשלב כזה ל master הוא כאילו הוספתי את ה commits שלי ,לא ל feature branch - אלא ישירות ל master.
התוצאה הרי הייתה זהה.

יש פה גם עניין של חיסכון ברמת המימוש הפנימי של גיט: בוודאי שמעתם ש branch הוא בעצם רק pointer.
על מנת לבצע FF כל מה שגיט צריך הוא להפנות את המצביע (branch) בשם "master" להצביע לנקודה של המצביע "feature branch".


אם מסיבה כלשהי חשוב לכם להדגיש את ההיסטוריה כפי שקרתה - אתם יכולים להורות ל git לבצע merge ללא FF.
זה כל הסיפור.



Git Revert


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




הפקודה git revert HEAD~2 (אנו מכוונים ל commit X, הרי הוא Head פחות 2 צעדים אחורה) - מנסה ליצור commit חדש המסומן כ X- אשר מהווה את ההופכי של X ומבטל את כל הפעולות שנעשו.
לאחר ש commit -X ייווצר - כל התוספות של y ו z - עדיין יהיו תקפות, לא ביטלנו אותן.

מתי הדבר שימושי? למשל כאשר יש בעיה בסביבת production או staging שאנו רוצים לפתור מהר, ואנו יודעים איזה commit אחראי לה. ניתן אח"כ לעשות revert ל X- - ולקבל בחזרה את השינויים של X למערכת.

אם יש התנגשות שגיט לא יודע לפתור (בד"כ הוא עושה עבודה יפה מאוד) - אזי הוא ייתן לכם לפתור את הקונפליקטים.
ניתן לקרוא ל git revert --abort (ממש כמו merge) - אם הסתבכתם בהתרה שלהם.

revert ניתן גם לעשות מתוך ה UI של github ולפתוח ממנו מיד Pull Request - מה שמאוד נוח.




מקור: https://www.slideshare.net/durdn/ninja-git-save-your-master


Git ReReRe (ידוע גם כ Re3)


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

השם הלא-שגרתי הוא קיצור לא Reuse Recorded Resolution או "שימוש-חוזר בפתרונות מוקלטים".

שימוש נפוץ בפקודה היא בסיטואציות בהן לא עובדים ב Continuous Delivery אלא ב long feature branches.
למשל: אני עובד על פיצ'ר במשך שבועיים-שלושה, ורוצה כל יום למשוך שינויים מה master.

לרוע המזל אני נתקל יום אחרי יום באותו ה merge conflict כי אני עובד על קטע קוד שעובר שוב ושוב שינויים גם על גבי ה master.

תסריט נפוץ אחר הוא כאשר עובדים עם rebase, ואז ישנם הרבה קונפליקטים דומים. למשל: אני עושה rebase ל branch עם 10 commits המכיל 4-5 קונפליקטים דומים על אותו האזור בדיוק. אני רוצה שגיט ילמד איך פתרתי את הקונפליקט הפעם הראשונה - ו"יסתדר לבד" בפעמים הבאות.

רהרהרה היא גם פקודה וגם קונפיגורציה. אנו אומרים לגיט להקליט את ה מיזוגים שאנו עושים (בעקבות rebase, merge, cherry-pick וכדומה) - בכדי שישמש בהם כ reference ל conflict resolution אוטומטי בעתיד.

הפעלת הקונפיגורציה הבסיסית נראית כך:
git config --global rerere.enabled 1

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


מקור: https://readyspace.com.hk/rerere

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

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

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




סיכום



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

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


שיהיה בהצלחה!



---

לינקים רלוונטיים

מדריך לשימוש בגיט רהרהרה


2018-04-05

ניקיונות גיט (פוסט קצר לפסח)

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

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

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

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

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

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


קצת רקע אישי הקשור לגיט:
  • אני עובד עם Github (כמו הרוב), מה שאומר שאני לא נוהג לבצע פעולות ב remote מתוך command line. 
  • אני עובד עם IntelliJ, מה שאומר שאני שחלק מהפעולות אני מעדיף לעשות ב GUI. בעיקר: 
    • git log, git blame, git diff 
    • ל intelliJ יש גם יכולת שימושית בשם Git Shelf - יכולת מקבילה ל git stash המאפשרת לאחסן כמות לא מוגבלת של "stashes" (ה IDE פשוט מאכסון את ה diffs בתיקיה נפרדת). 
  • אני עובד עם Ohh my Zsh מה שמאפשר לי כמה קיצורים ו autocomplete שלא נמצאים ב shell הסטנדרטי. זה כנראה לא משפיע הרבה על מה שאכתוב כאן - אקפיד להשתמש בשם הפקודה המלאה ולא בקיצורים.






ניקוי קבצים


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

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

הנה כמה פעולות שימושיות "להשתחרר" מהמצב:

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



Test-Delete untracked files: git clean -n
Delete untracked files (not staging): git clean -f
clean היא פקודה שמטרתה לנקות קבצים שאינם באינדקס (קרי: לא tracked). הגרסה n- מציגה רשימה של קבצים למחיקה, ו f- מוחקת אותם בפועל.


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

. git add ואז
git reset --hard

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

הסבר קצר: לפקודה git reset יש 2 צורות:
  • הסרה של קובץ / path מהאינדקס. ההופכי ל git add. הווריאציה הזו פועלת כאשר מצוין path (למשל: .)
  • איפוס ה branch הנוכחי (HEAD) למצב של commit מסוים - כאשר לא מצוין path.
קצת מבלבל שיש 2 פעולות עם אופי שונה תחת אותה הפקודה!

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


לכאורה ניתן לבצע את פעולת "revert" מתוך ה IDE אבל בכמה מקרים (למשל: קבצים חדשים) - יידרשו עוד כמה צעדים ידניים. גישת ה add-reset היא המהירה ביותר (עד כמה שידוע לי).









התחרטתי!


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


התחלתי merge אבל הוא הסתבך לי

git merge --abort

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


עשיתי commit ל master או ל branch אחר שלא התכוונתי

git reset HEAD~1

הנה עוד שימוש בווריאציה השנייה של git reset - איפוס ה HEAD ל commit מסוים. 
על הפקודה הזו ניתן לחשוב כמו HEAD = HEAD-1:
  • החזר את ה branch ל commit אחד אחורה (ניתן להחליף את המספר 1 בכל מספר אחר).
  • כל השינויים שנכללו ב commits ש"בוטלו" - יועברו ל working directory.
מכאן אני יכול לבצע:

git checkout desired_branch
. git add
"..." git commit -m 

והנה כל השינויים נמצאים כ commit ב branch שאליו התכוונתי.


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



בגדול, כל פעם ש git log מעורב - יש יתרון לממשקי GUI ע"פ ה command line (לטעמי).
בהפעלת הפעולה, IntelliJ פותח 4 אופציות. האופציה שציינתי נקראת "mixed" - אבל אתם יכולים לבחור בכל אופציה שנשמעת לכם הגיונית ורצויה.




לנקות branches


branches באים הולכים: נוצרים, נושאים שינויי קוד, ואז ממורג'ג'ים ל branch אחר - ואז אין צורך בהם.
אם אתם מיישמים Continuous Integration - רוב הסיכויים שייצרו כמה branches חדשים כל יום.
מכירים את המצב שיש לכם 5 או יותר branches מקומיים שאתם לא בטוחים מה המצב שלהם?
אתם רוצים לעשות push ולמרגג' את מה שנותן, למחוק אותם - ואז להתחיל עבודה חדשה.

מסובך? 

העצה הכי טובה שיש לי היא למחוק בזוגות, branches מקומיים ומרוחקים - מיד לאחר ביצוע merge ב github.

git branch -d branch_name :
  1. ימחק branch שהוא fully merged, ל repository המרוחק, או המקומי
  2. ימחק (עם warning) את ה branch אם הוא push ל remote (ואין לו עדכונים).
  3. לא ימחק אם אין remote tracking ו/או יש שינויים מקומיים שלא עודכנו ל remote.
זוהי פקודה בטוחה יחסית. אם המקרה השני קרה - אפשר לחזור ממנו בעזרת git checkout ל branch שנמחק. הקוד נמצא ב remote.
את ה branch ב Github - מוחקים בעזרת ה UI.


אפשר לאטמט את התהליך המקומי ולהפעיל פקודה כמו:

git branch | grep -v "master" | xargs git branch -d 

עוברים על כל ה branches המקומיים
grep -v יוציא ה master מהרשימה (אותו לא נרצה למחוק)
xargs מפעילה את הפקודה שאחריה עם פרמטר של מה שמגיע מה pipe (כלומר: stdin). כלומר: תנסה למחוק את כל ה branches, מלבד master, בצורה "בטוחה".

עדיין צריך לשים לב ל warnings ולהחזיר (בעזרת checkout) את branches שלא התכוונו למחוק.
יהיו עדיין branches שהפקודה לא תמחק. ירשם error בנוסח "the branch ... is not fully merged". אלו:
  • branches עם commits מקומיים - אולי שכחנו לעדכן ל remote? אולי ויתרנו על הקוד הזה?
  • branches ללא remote tracking (מעולם לא עשינו push ו/או הפעלנו git fetch --prune או פקודה דומה).

אז מה עושים עם ה branches שלא נמחקו עם git branch -d?
כאן יש עוד עבודה ידנית. עדיין לא מצאתי דרך פשוטה יותר:
  • להיכנס לכל branch
  • לבדוק את ה log לראות אילו commits קיימים
  • אם זה לא מספיק - לעשות diff ולראות מה השינויים בקבצים. האם נרצה אותה?
  • לדחוף (git push) ו/או למחק (git branch -D branch_name) את ה branch.
כרגיל, כאשר יש עבודה עם git log - יש יתרון גדול לעשות את העבודה ב GUI.


שווה אולי להזכיר שיש גם פקודות כגון:

git branch --merged branch_name

המספקת לנו רשימה של branches שממורג'ג'ים ל branch_name.
כאשר רוב ה merges נעשים ב remote (למשל: GitHub) - היא פחות שימושית

ניתן גם לבדוק אלו branches שיש להם remote tracking (להלן r-) מורג'ג'ו ל master המרוחק:

git branch -r --merged origin/master

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


---

טיפ קטן למשתמשי Ohh my Zsh:
לעתים המלצות ה autocomplete ל gco (קרי git checkout) כוללות כל מיני branches ישנים.

git fetch -p שזה בעצם git fetch --prune

מנקה את ה metadata על branches שהם כבר לא remote tracked - מה שמנקה הצעות autocomplete לא רלוונטיות, אל הניקוי הזה גם גורם לפקודת git branch -d לכישלונות כי היא לא יודעת מה מצב ה branch המרוחק.

הטיפ: לקרוא ל git fetch -p רק במצב שכל ה branches המקומיים נקיים או שאין לכם בכלל branches מקומיים. זה יחסוך לכם כאבי ראש.

---



טיפ קטן לסיום:


איך רואים ב IntelliJ השוואה כמו זו של Github?


ה compare של Github נראה קצר יותר וממוקד יותר מ compare ב IDE?
אתם מוצאים את עצמכם עושים git push רק בכדי ליהנות מה compare של Github ולצפות בהתקדמות שלכם בכתיבת קוד?

אתם יכולים לבצע השוואה דומה גם ב IntelliJ.

אני מניח שאתם נוהגים לעשות compare מתוך התפריט בפינה התחתונה של ה IDE:



ואז compare ל branch המוביל:



עד כאן טוב ויפה, אבל כנראה שהפעולה הבאה שלכם היא לבחור ב tab של files (מסומן ב x אדום):



התוצאה תהיה לראות את כל השינויים מול ה master - גם כאלו שלא אתם הכנסתם, אלא אתם גוררים עם ה branch. זה לא יעיל.

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

הנה עוד כמה settings ב view של ה compare שידמו יותר ל Github:




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



זהו לסיום.
מקווה שהפוסט יהיה שימושי לכמה אנשים.

שיהיה בהצלחה!




---

לינקים רלוונטיים

https://zippyzsquirrel.github.io/squirrel-u/1_SquirrelU/4_GitHub/1_introToGitAndGitHub/ - מדריך הכולל גם פרטים כיצד לעבוד עם כלי ה git של IntelliJ.


2018-03-17

4 חטאים של פיתוח תוכנה בן-זמננו [דעה]

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

מכאן נוצרת נכונות רבה ללמוד, אבל הרבה פעמים קשה למצוא תוכן משמעותי ללמידה:
  • בספרי תכנות, למשל, הפרקים המאוחרים (לעתים מאוגדים כ "Advanced Topics") הם לרוב נושאים פחות שימושיים ביום-יום. ביום פקודה - אפשר להשלים את הידע נקודתית. זו גישה מאוד הגיונית.
  • היתרון מלהכיר עוד Frameworks הולך ופחות ככל שאתם מכירים יותר Frameworks. אם אני מכיר כבר שני Web Frameworks בצורה טובה - איזה יתרון באמת יהיה לי מללמוד את השלישי?!
  • אפשר ללמוד אינספור כלים וספריות, אבל אם לא עובדים בהם בצורה משמעותית - זה יידע שלא יעשה בו שימוש ו/או יישכח במהרה.
  • ישנם נושאים קצת יותר רחוקים מכתיבת הקוד עצמו, אך מספיק שונים בכדי לספק לנו "קרקע בתולית ללמידה". הרבה פעמים יש להשקיע בהם השקעה משמעותית מאוד - עד שנראה תמורה אמיתית ביום-יום שלנו. למשל: Machine Learning, מערכות מבוזרות, או Big Data. לא בטוח שזה אפיק משתלם עבור רוב אנשי-התוכנה.

----

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

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

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

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

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




אז מה יש לנו?





TDD - איך כותבים בדיקות מוצלחות, ואיך כותבים קוד שקל לכתוב לו בדיקות מוצלחות.


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

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

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

מה הן בדיקות טובות? יותר קל לי להציג אלמנטים נפוצים של בדיקות לא טובות:
  • אנשים מגזימים בכמות הבדיקות המערכתיות (System Test, Integration Tests) על חשבון בדיקות ממוקדות. גלידה ולא פירמידה. זה כ״כ נדוש ושחוק, אך עדיין - טעות שממשיכה ונעשית.
  • אנשים לא מבודדים Pure Business Logic משאר הקוד לצורך unit tests - ואז באמת קשה מאוד מאוד לכתוב ולקבל את היתרונות של unit tests.
    • נתקלתי הרבה פעמים במצב הזה, וזו בעיה שיחסית קל לתקן, ברגע ש״נופל האסימון״ - ומשנים גישה.
  • אנשים כותבים יותר מדי קוד בדיקות - מה שמאט את העבודה שלהם, ומקשה על ביצוע שינויים במערכת:
    • גם בדיקות שהן overfit למימוש ארעי (situational), כלומר תנאי שמתקיים - אך אינו חשוב ועקרוני לפעולת המערכת / הביזנס. בהמשך הוא ישתנה, לא תהיה בעיה עסקית - אך הבדיקות יפלו וידרשו עדכון.
    • גם בדיקות שהן יתירות (בודקים את אותו הדבר שוב ושוב באופנים שונים). כל שינוי של מימוש קוד - ידרוש סדרה של שינויים בקוד הבדיקות - מה שיגרום לנו לרצות לעשות פחות שינויים.
      • יעילות מגיעה מניהול סיכונים נכון: האומץ לצמצם את כמות הבדיקות (לא לכתוב בדיקות מסוימות), מתוך הבנה אלו בדיקות חשובות ומשמעותיות יותר.
  • אולי הכי גרוע: בדיקות ועוד בדיקות שנכתבות (ומתוחזקות!) מבלי שהן מגרדות את פני השטח. הן בקלות יכולות לעבור - בזמן שמשהו עקרוני ולא טוב קורה ב flow. בקיצור: בדיקות לא-משמעותיות.
    • זכרו: אם הבדיקות שלכם אף פעם לא נשברות - זו לא סיבה לגאווה. זה אומר שבזבזתם את הזמן בכתיבת בדיקות שלא אומרות כלום.
  • אנשים שהתייאשו מבדיקות ו״למדו״ (אבוי!!) - שבדיקות הן נושא overrated ומיותר.
    • זהו מצב שמאוד קשה להתאושש ממנו.

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

המחשבה שאם אתם מכירים את הספרייה שאיתה עושים בדיקות (JUnit5, Jasmine, RSpec, Sinon), אזי אתם יודעים "לכתוב בדיקות טובות" - היא שגויה מיסודה. חפשו את העלות/תועלת: כמה השקעה יש בכתיבת ותחזוקת הבדיקות - מול כמה זמן הן מקצרות בתהליכי ה debug ותיקון שגיאות.


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

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

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

זה לא שהייתי מקודד טוב יותר - זו הטכניקה שעבדה.







Refactoring אקטיביסטי


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

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

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

המון! מן הסתם.

ברגע הזה, שקורה לרובנו על בסיס יומי, עומדות בפנינו שתי ברירות:
  • להמשיך הלאה.
    • יש לי מנהל עם ״סטופר״ שיבוא בתלונות אם לא אדלוור פיצ'רים מהר.
    • יותר גרוע: שינוי בקוד הוא סיכון ליצירת באג. אם יש באגים שנוצרו על ידי - אני יוצא לא-טוב. (איפה ה Unit tests שלכם עכשיו, הא?)
  • לבצע Refactoring ולהחזיר את הקוד לרמה אופטימלית X (כלומר: רמה טובה, אבל לא מושלמת. שלמות היא בזבוז).
    • Refactoring אינו צריך, ועדיף שלא יהיה "פרויקט ענק". הוא יכול להיות בכל commit שלישי.
    • אם שומרים על רמת קוד טובה לאורך הזמן - יהיה הרבה פחות צורך בפרויקטי ענק.
אז מה אתם בוחרים?

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

החטא של המפתחים הוא שהם תורמים את חלקם למעגל המזיק הזה - ובעצם פוגעים באינטרסים שלהם.
התמריץ לשמר את הקוד ברמה "אופטימלית X"  הוא לא רק עניין של ערכים "אני בעד קוד יפה", חלילה!
יש פה אינטרסים מעשיים:
  • קוד שמתוחזק ברמה גבוהה - יאפשר להוסיף פ'יצרים נוספים בצורה קלה ומהירה יותר, ועם פחות תקלות.
    לאורך הזמן השאלה צריכה להיות: האם אתם רוצים לעבוד בקוד מתוחזק, או בקוד "עולם שלישי"? באיזו סביבה אתם חושבים שתתפתחו, אישית - בצורה טובה יותר?
  • כאשר בוחשים בקוד - רמת העומק וההבנה האישית שלנו את הקוד, ומה שקורה בו - צומחת בקצב אחר לגמרי.
    • אני לא יכול להדגיש זאת מספיק: מי ששובר את הקוד (או לפחות מסתכן בשבירה) - הוא מי שמבין אותו לעומק. "לשבת על הברזלים" זו אסטרטגיה נוחה לטווח הקצר - אך נחותה לטווח הארוך.
עוד אלמנט חשוב הוא היכולת שלנו לראות כיצד הקוד יכול ללבוש צורות שונות - והיכולת להעביר את הקוד בקלות מצורה לצורה: אולי functional? אולי לולאת foreach? אולי break ואולי exceptions.
  • בעיות שונות בקוד יפתרו באלגנטיות רבה יותר בעזרת צורות שונות של קוד. 
    • כאשר אנשים מקובעים לתבנית אחידה / סגנון אחיד - זה מגביל!
    • לאנשים רבים, גם כאלו עם ניסיון של שנים - חסרה ממש הגמישות הזו: קשה להם לקרוא ולהבין קוד בסגנון שונה, והם חוזרים וכותבים קוד בצורה "שהם רגילה אליה" - גם במקרים בהם היא מסורבלת וקשה לקריאה.
  • Refactoring תכוף - הוא דרך נהדרת ללמוד ולהתנסות בצורות קוד שנות. זה האינטרס האישי שלכם!
  • שווה לציין גם טכניקה בשם "Coding Dojo״ שאמורה לפתח מנעד רחב יותר של סגנונות קוד:
    • מתכנסים כמה אנשים בחדר ופותרים תרגיל קוד קטן כאשר מחליפים ידיים כל פרק זמן נתון (מעבירים את המקלדת מאדם לאדם). עוד נוהג הוא לעשות את אותו התרגיל - מספר פעמים. בכל פעם - תהיה תוצאה קצת אחרת.
    • נ.ב. אני נוטה להאמין שיעילות המפגש שכזה היא ביחס ישיר לאדם המוכשר ביותר בסגנונות קוד שנוכח בו.

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

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

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






Design to Go


"ביצוע Design הוא תהליך מתמשך, ולא עוד שלב במחזור פיתוח התוכנה (כמו שפעם חשבו ב Waterfall)".
כולנו יודעים לדקלם זאת - אבל רבים מאתנו לא עושים זאת.
אנחנו מפספסים:
  • עבודה ב Small Batches.
  • יצירה של Short and Effective feedback cycles.
  • בחינת אלטרנטיבות - מתוך ההבנה שיש יותר מדרך משמעותית אחת לסדר קוד ו/או לפתור בעיה.
    • ״קו האפס״ הוא פתרון יחיד שעובד - ומשם משפרים. 
    • אחרת: אנחנו עובדים על עצמנו. לא משנה כמה מלבנים ציירנו בדרך.
  • כאשר ״תקיעה״ בתהליך הדזיין, מובילה אותנו לוותר עליו - במקום לעבור ל Exploration.
כבר דיברתי הרבה בנושא בהרצאה שלי ברברסים. אין טעם לחזור.



מקור: Integrating and Applying Science" (pg. 136) - http://ian.umces.edu/press/publications/259/


Modeling


Modeling היא לא פרקטיקה נפוצה בקורות החיים של אנשים. 
המונח ״Medling״ כנראה מובן לרוב האנשים, אך הוא לא נתפס כנושא בעל חשיבות עליונה - שכדאי לפתח.
  • הזכרנו כבר שנקודת מפתח ב Design היא בחינת אלטרנטיבות.
  • החלק המשמעותי באלטרנטיבות הללו הוא לא ״אובייקט גדול״ מול ״שניים קטנים״ - אלא מידול שונה של האובייקטים העסקיים. למשל: ״תשלום, הכולל ניסיונות תשלום״, מול ״נסיונות תשלום הכוללים תוצאה״.
  • ״גמישות לדרישות עתידיות״, ו״פשטות״ הם BuzzWords - אך הם גם סופר-משמעותיים במבחן התוצאה. 
    • מודל פשוט וטבעי לביזנס - יכול בהחלט להכפיל את התפוקה של הצוות.
      מיומנות מעטות בעולם התוכנה עשויות לגרום להשפעה (impact) רבה שכזו!
  • היכולת לעשות modeling נכון נובעת מניסיון תמידי להבין את הביזנס והצרכים + הפעלה של חשיבה ביקורתית.
    • קל לצייר בראש מודל - שלא ממש מתאים לביזנס. חשוב לתקשר ולאמת אותו.
    • לא תמיד אנשי הביזנס יתחברו למודל - וחשוב גם לנסות ולאתגר אותם.
  • Modeling לא נעשה רק בשלב דזיין - אלא גם כתהליך refacotring, שינויים קטנים כל הזמן.
  • Modeling מתקשר בד״כ למידול של אובייקטים עסקיים, אך הפרקטיקה נכונה גם למודל טכני (מודל concurrency, מודל eventual consistency, מודל sevurity):
    • שואלים ומאתגרים כל הזמן מה הם הצרכים
    • מנסים למצוא מודל פשוט ואלגנטי ככל האפשר, פשוט ע״י איטרציות של שיפורים במודל.
    • מתקשרים את המודל - כך שיהיה רעיון משותף, ולא ״מחשבה פרטית״.
  • איך לומדים לעשות מודלינג?
    • ע״י צפיה בדוגמאות של מודלים. למשל הספר PEAA (דוגמה יפה: המודל של Money), או הספר המעולה (אך קצת מיושן): Analysis Patterns - של אותו המחבר.
    • ע״י בניית מודלים והפקת לקחים אישיים.
אין מה לומר: עבור מי שכבר כותב קוד בצורה שוטפת, אני מתקשה לחשוב על מיומנות יותר שימושית ומועילה לפיתוח תוכנה מ Modeling: כל טעות מידול עלולה לגרור לעשרות (מאות?) שעות נוספים של כתיבת קוד. שום פלאג-אין ב IDE, ושום Framework ״אלוהי״ לא יקזזו את זה.

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



    ה Killer instinct


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

    כשאתם נתקלים ב״Killer Instinct״ - קשה להתעלם ממנו.
    • זה השילוב של הבנת ביזנס, חשיבה ביקורתית, קריאה נכונה של הארגון (מי מדבר שטויות, מי יודע), קצת חוצפה (ממי להתעלם, למי להתייחס), והאומץ לבצע שינויים / לכתוב קוד שיש לו חסרונות ברורים - לצד יתרונות ברורים, כמובן.
      • תמיד נתקלתי ב Killer Instinct בצמידות לנטייה לגעת בקוד ולשנות אותו. חוסר פחד, ביחד עם סקרנות ורצון לחולל שינויים.
        אני נוטה להאמין שיש פה גם אלמנט של סיבתיות: הניסיונות הקטנים לשפר את הקוד -> יוצרים הבנה עמוקה של הקוד (עם הזמן). הבנה עמוקה של הקוד -> מאפשרת את ה״מאסה הקריטית״ של העומק - הדרושה בכדי לבצע שינויים משמעותיים במערכת בזמן קצר.
    • ״להתעסק״ עם הקוד בלי שיש בדיקות טובות - לא כדאי. הקוד ישבר, וההתעסקות תהפוך לעניין כואב ומתסכל.
    • הבנה עמוקה של הקוד, ללא הבנה של הביזנס - עשויה לפספס את האימפקט:
      אתם עושים שינוי עמוק במערכת, שאף אחד לא האמין שאפשרי - אבל אז גם לאף אחד לא אכפת הוא נעשה, כי הוא פשוט לא מעניין.
    • בכדי ליצור אימפקט, חשוב להבין את הביזנס. הבנה של הביזנס נבנית מתוך Modeling.
    • בכדי שהתוצר יהיה טוב יותר, ומשמעותי גם לאורך זמן - חשוב גם לדעת איך לעשות Effective Design.

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

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


    שיהיה בהצלחה!



    2018-02-24

    Bulkheads - דפוס עיצוב של חוסן (Resiliency Design Pattern)


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


    [ב]


    ירידה לעומקו של עניין


    רעיון ה bulkhead[א] הוא רעיון עקרוני ליציבות של מערכות, שאותו ניתן לראות בשימוש גם בעולם התוכנה.

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

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

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


    הנה שתי דוגמאות מוכרות ליישום של bulkhead שאנו מכירים מהיום-יום:
    • availability zones ב AWS (או המקבילה בעננים אחרים) - כשל של AZ יחיד יפגע בשירות (בטווח הקצר) - אך יאפשר לנו להמשיך את השירות כרגיל ב AZs האחרים. 
      • לצורך כך מושקעים ב Amazon מאמצים רבים על מנת לוודא ש AZ אינם תלויים זה בזה, ושכשל באחד ה AZ (הצפה, נפילת מתח, בעיית תוכנה, וכו') - לא יגרור כשל של ה AZ האחרים.
      • כמובן שבתכנון מערכת המשתמשת ב AWS עלינו ליצור יתירות של שירותים חיוניים (למשל: NAT gateway או בסיס-נתונים) על מנת שנוכל להמשיך ולרוץ בזמן ש AZ אחד כשל.
    • תהליכים במערכת ההפעלה - מערכת ההפעלה יוצרת הפרדה בין תהליכים (processes) שונים כך שכשל בתהליך אחד לא ישפיע על תהליכים אחרים: תהליך אחד קורס - והשאר יכולים להמשיך לרוץ ללא הפרעה.
      • למען הדיוק הטכני שווה לציין שההפרדה הזו אינה bullet proof כאשר מדובר בגישה למשאבים משותפים.
        למשל: תהליך שגוזל 100% CPU עלול להיות מתוזמן (לחלופין) על כל ה cores של המכונה ולשתק בפועל את כולה. עלינו להצמיד את התהליך (בעזרת CPU binding / affinity) ל core מסוים - בכדי לקבל הגנה טובה בפני תסריט ה "CPU 100%". עניין דומה קיים לגבי זיכרון, גישה ל I/O, או כל משאב משותף אחר. 
    היישום שאני רוצה להתמקד בו הוא יישום אפליקטיבי של מערכת (ווב).

    יישום בסיסי של bulkheads: להפריד את השרתים שלנו לשני clusters שונים (ומבודדים זה-מזה) ולנתב בקשות שונות ל cluster שונה. החלוקה יכולה להיות עבור microservice בודד, קבוצה של microservices, או אולי אפילו כל המערכת.



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

    החכמה ביישום bulkhead מוצלח היא חלוקה סלקטיבית ע"פ שני קריטריונים:
    • מאפייני כשל (failure conditions) - כך שתעבורה מסוג I עלולה לכשול בעוד תעבורה מסוג II עשויה לעבוד כרגיל.
    • יתרון עסקי (financial benefit) - כאשר יש חשיבות עסקית מאחורי סוגי התעבורה השונים שעשויה להצדיק מצב בו תעבורה סוג I שורדת בעוד תעבורה סוג II כושלת.
    Bulkhead מוצלח עשוי להיות על בסיס שני הקריטריונים, או רק אחד מהם.


    הנה כמה דוגמאות ליישום של Bulkhead ברמה האפליקטיבית:

    הדוגמה הקלאסית היא כנראה הפרדה בין לקוחות משלמים ללקוחות לא-משלמים. 
    נניח: אתר שנותן שירות מוגבל בחינם, אך שירות משופר בתשלום (freemium).
    שימו לב שהחלוקה היא עסקית.
    וריאציה מקובלת: שני clusters:
    • Cluster A - ללקוחות משלמים
    • Cluster B - ללקוחות שאינם משלמים.
    אם יש בעיה בפיצ'ר של לקוחות לא-משלמים שגורם לבעיה - לקוחות משלמים יכולים (ובצדק!) להמשיך ליהנות משירות תקין.
    אפשר לשים יותר חומרה ומשאבים, קונפיגרציות יותר אמינות (גם אם עולות יותר) - ב cluster של הלקוחות המשלמים.

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

    תת וריאציה היא ש Cluster B יקבל תעבורה של שני סוגי הלקוחות: משלמים ולא-משלמים.
    במקרה של תקלה - אפשר לדחות לקוחות לא-משלמים כליל מהמערכת. אם יש משהו שיציל את התעבורה של לקוחות משלמים (נניח: עוד חומרה) - אדרבא!
    אם יש כשל שנובע מ"פיצ'ר חינמי" (נניח: פרסומות) - יש הגיון עסקי רב לבודד את הכשל מלקוחות משלמים.
    הוריאציה הזו הגיונית ככל ש Cluster B גדול מ Cluster A (נניח: פי כמה מונים).



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

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

    בדוגמה הזו יש ל bulkheads פוטנציאל גדול יותר להשיג שיפור ממשי מהדוגמה הקודמת.





    דוגמה: הפרדה לפי שווקים

    למשל:
    • Cluster ללקוחות ארה"ב
    • Cluster ללקוחות מערב אירופה
    • Cluster ללקוחות מזרח-אירופה
    • Cluster ללקוחות אנגליה
    בהנחה שעבור כל מדינה יש חלקי קוד ייחודים המתאימים לרגולציה ו/או settings מעט שונים שהם מקובלים יותר (בגלל הבדלים בין השירות במדינות) - העלולים לגרום לתנאי כשל שונים.

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

    ניתוח של תנאי הכשל (אלו מדינות משתמשות בפיצ'רים שונים --> חשיפה לתנאי כשל פוטנציאלים שונים) והמשמעות העסקית מובילה אותנו לחלוקה ל-4 clusters.

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

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



    דוגמה אחרונה: מנגנון חדש מול מנגנון ישן ("canary release")

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

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



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






    אזהרת Patterns!!! (גנרית)


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

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

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

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


    שיהיה בהצלחה!


    ---

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

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



    2018-02-17

    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק ח': קוטלין וג'אווה (interoperability)

    פוסט זה הוא המשך של:
    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק א': הבסיס
    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק ב': פונקציות
    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק ג': מחלקות
    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק ד': עוד על מחלקות, אובייקטים, ועוד...
    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק ה': DSLs
    קוטלין (Kotlin) למפתחי ג'אווה ותיקים - חלק ו': Collections ו Generics


    הפעם אני רוצה לדבר על Interoperability בין קוטלין וג'אווה.

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

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


    מקור


    כיצד null יכול לצוץ לו בקוטלין ללא הודעה מוקדמת?


    כבר כמה פעמים נשאלתי את השאלה: "האם אפשר לרשת מחלקת ג'אווה בקוטלין? קוטלין בג'אווה?"

    בוודאי שאפשר! אחרת לא הייתי אומר ש interoperability ביניהן כ"כ מוצלח.

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

    ערבוב של קוד קוטלין וג'אווה לצורך רצף הקריאות. במציאות כמובן שהקוד ישב בקבצים נפרדים.
    1. יצרנו מחלקה מופשטת A בשפת ג'אווה.
    2. הרחבנו את המחלקה בג'אווה A - בעזרת מחלקה בקוטלין B.
      1. מכיוון שברירת המחדל בקוטלין היא מחלקה final - עלינו להגדיר אותה כ open ע"מ שקוד הג'אווה יוכל לרשת את המחלקה C.
    3. ואכן הרחבנו את המחלקה בקוטלין B בג'אווה, ללא בעיה. כל שפה שומרת על הקונבנציות שלה (במידת האפשר)
    4. הממ... ה IDE מעיר לי פה משהו: 
    Not annotated method overrides method annotated with @NotNull 

    מה זה?
    אני לא רואה Annotation בשם NotNull@ בקוד.



    מה? java.lang.NullPointerException? - אבל אני כותב בקוטלין!?



    בכדי להבין מה קורה, נחזור שלב אחר אחורה - למחלקה KotlinB.

    במחלקה הזו דרסנו את המתודה ()getHelloMessage שהוגדרה בג'אווה.
    ערך ההחזרה של המתודה שהוגדרה בג'אווה הוא String, אבל מה זה אומר עבור קוטלין: String או ?String, אולי?



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


    והנה השימוש שלה בקוטלין:


    ה IDE מסמן לי שערך ההחזרה של המתודה הזו הוא !String.

    אין טיפוס כזה בקוטלין, וטעות נפוצה היא להניח ש !String הוא ההיפך מ ?String - כלומר: String שהוא בהכרח לא null.

    מה שבאמת ה IDE מנסה לומר לנו הוא שהוא לא יודע אם ה String הוא null או לא. אני חושב שתחביר כמו [?]String היה יכול להיות יותר אינטואיטיבי.


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

    other?.java?.object?.always?.might?.be?.null()


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

    זה נוח, אבל גם יכול לגרום לשגיאות בלתי צפויות.


    הנה דוגמה מהחיים:


    jdbi הוא פריימוק רזה (וחביב) הכתוב בג'אווה, ומאפשר גישה לבסיס הנתונים.
    אופן השימוש בו הוא להגדיר interface או abstract class עם מתודות ומוסיף להן annotation עם השאילתה שיש לממש.
    jdbi, בעזרת reflection, מג'נרט (בזמן ריצה) אובייקט DAO שמממש את הממשק שהגדרתי. התוצאה היא אובייקט שמבצע את השאילות שהגדרתי ב annotations. קוד עובד.

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

    ...עד הרגע שאני מפעיל את השאילתה עם job_id שלא קיים - וחוזר לי null.
    מתישהו אני "חוטף" NullPointerException.

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

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

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



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


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

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



    השימוש ב annotation מסיר מה IDE את הספק:


    ואז הוא יכול להגן עלי.

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


    באופן דומה, אגב:

    (Mutable)List<String>

    הוא סימן שה IDE מספק שמשמעו: רשימה שייתכן שהיא mutable, וייתכן immutable. הקומפיילר לא מסוגל להגיע למסקנה בעצמו.

    הנה דוגמה לביטוי מורכב:

    • הרשימה ו/או האיברים בה עלולים להכיל ערך null.
    • הרשימה עשויה להיות mutable או לא.

    קוד הג'אווה מאחורי המתודה ()getStrings הוא זה:


    מה שמוביל אותנו לעניין נוסף שכדאי להכיר:
    כאשר מתודה בג'אווה נקראת ב naming של JavaBeans, כלומר: ()getXxxx או ()setXxxx - קוטלין מתייחסת אליהם כתכונה בשם xxxx.

    הנה הקוד בקוטלין שקורא לקוד הג'אווה הנ"ל:


    אתם רואים שהשלפנים (getters) שכתובים בג'אווה נראים בקוטלין כמו תכונות לכל דבר.
    מכיוון ש true היא מילה שמורה בקוטלין, יש לעטוף (escaping) אותה בגרש מוטה.

    באופן סימטרי, תכונות (properties) שהוגדרו בקוטלין כ yyyy יופיעו בקוד הג'אווה כמתודות ()getYyyy ו/או ()setYyyy.


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


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




    חשיפה מתוכננת


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

    אם יש לי תכונה בשם yyyy בקוטלין (מה שטבעי בקוטלין), הגישה אליה תהיה בעזרת getYyyy ו setYyyy - מה שטבעי בג'אווה.

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


    אאוץ. אאוץ!!

    הנה רשימת בעיות:
    • כאשר אני קורא לתכונה now מג'אווה - שם הפונקציה מופיע כ ()getNow, ומסיבה כזו או אחרת אני רוצה להשתמש בשם now כ field.
    • המילה transient היא מילה שמורה בג'אווה - אך לא בקוטלין. אי אפשר לקרוא לפונקציה הזו מתוך ג'אווה, ואין escaping בג'אווה המאפשר להשתמש בשמות שאינם תקינים בשפה.
    • אני לא יכול ליהנות מהערך ברירת המחדל של המתודה repeat. אין קונספט של default value בג'אווה - ולכן אני נדרש לשלוח את שני הפרמטרים בכל קריאה. בריבוי קריאות - זה יכול להיות מעצבן!
    • יצרתי companion object על מנת "לחקות" מתודות סטטיות בג'אווה - אבל הדרך לקרוא ל foo היא באופן: ()KotlinProducer.Companion.foo. מסורבל!


    מה עושים?

    הנה הפתרון, מבוסס annotations - אך עובד:


    JvmOverloads היא הנחיה להשתמש ב default values על מנת לג'נרט מופעים שונים של פונקציות בקומבינציות השונות, מה שנקרא בג'אווה Method Overloading. אני מניח ששאר ה annotations הן self-explanatory.

    הוספתי גם דוגמה לשימוש ב extension function. איך משתמשים ב extension functions מתוך ג'אווה?!


    הנה קוד הג'אווה שמשתמש בקוד הקוטלין, "בהנאה":


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

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


    סיכום


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

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

    ה interoperability בין ג'אווה וקוטלין פשוט עובד!

    יצא לראות לא מעט קוד קוטלין (צד-שרת) שעובד:
    • בצורה אינטנסיבית עם ספריות של ג'אווה.
    • ספריות ותיקות, שנכתבו לג'אווה - עוד לפני שקוטלין הייתה מעניינת.
    • ספריות שמבצעות reflection והורשה לקוד הקוטלין שנכתב (למשל: JDBI, Guice, או Jackson שמקודד עשרות רבות של מחלקות ל json ובחזרה לקוטלין)
    • והעבודה הייתה בסה"כ חלקה מאוד!
      • במקרים מעטים היה צורך / או היה יפה יותר להשתמש בכלים שסיפקתי בפוסט הזה.
      • במקרים מעטים נאלצנו לכתוב קוד "java-like" בקוטלין, על מנת שדברים יעבדו. עם הזמן צצו wrappers לקוטלין שהקלו על הדברים, ואפשרו להשתמש בסגנון "קוטליני" בחופשיות.



    שיהיה בהצלחה!