על תפקיד ה Product Owner והשפעתו על הארכיטקטורה

בסקראם, ה Product Owner (בקיצור PO) נדרש למלא 2 תפקידים שונים בתכלית:
  • מצד אחד: לחבוש את “מגבעת המרחבים” ולחלום על חזון רחוק, שכרגע אולי ואינו ברור או אפשרי.
  • מצד שני: לחבוש את “קסדת בוב הבנאי” ולהוביל בנייה הגיונית של מוצר מעשי.

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

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

ממש פיצול אישיות!

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

שייך לסדרה: אג’ייל – מתודולוגיות פיתוח רזות

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

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

אקסל כ”מוצר עוצמתי ורב יכולות עבור המשתמש המקצועי”. מקור: http://nizagreen.blogspot.co.il/2012/01/partes-de-excel.html

או אולי משהו כזה… (עבור ה PO בעל הנטיות השיווקיות):

“אקסל מוכן להשקה”. מקור: מייקרוסופט

“Begin with the end in mind” היא אחת העצות שנותנים למנהלי מוצר בכדי שיהיו יעילים.

תחת הכובע של בוב הבנאי, התמונה הראשונה שעל ה PO לראות צריכה להיות משהו כזה:

היכולות הבסיסיות ביותר באקסל, מפורטות ומדויקות. מקור: וויקיפדיה.

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

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

מה יקרה אם יהיה לנו ״איש חזון ושיווק״ טוב, שלא יחבוש את ״קסדת בוב-הבנאי״?

בואו ניקח לדוגמה סיפור בו אנו רוצים להוסיף למוצר שלנו יכולת חדשה: בדיקת איות.
ה PBIs[ב] של היכולת, כפי שהוגדרו על ידי ה׳ PO, נראים כך:

  1. כמשתמש, אני רצה שבדיקת האיות תהיה מהירה, תוך כדי הקלדה (= שיפורי ביצועים)
  2. כמשתמש, אני רוצה שיוצגו לי כתיקון לשגיאת הכתיב, קודם מילים נפוצות יותר בשפה
  3. כמשתמש, אני רוצה שיוצגו לי הצעות לתיקון ע”פ מילים שמופיעות במסמך
  4. כמשתמש שמגדיר את העדפותיו – אני רוצה לקבוע אם בדיקת האיות תהיה תוך כדי הקלדה וכמה משאבים יוקצו לה
  5. כמשתמש שמגדיר את העדפותיו – אני רוצה לקבוע אילו מילונים יהיו בשימוש
  6. כמשתמש שמגדיר את העדפותיו – אני רוצה לקבוע חוקים ספציפיים לבדיקת האיות בשפה נתונה
  7. כמשתמש בגיליון – אני רוצה לראות סימון אדום כאשר יש לי שגיאת כתיב
  8. כמשתמש בגיליון – אני רוצה לקבל המלצות לתיקון
  9. כמשתמש בגיליון – אני רוצה להיות מסוגל להחליט שמילה שנתפסה כשגיאה היא תקינה – ולהוסיף אותה למילון

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

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

אנו נוגעים כעת במרכז העצבים של מתודולוגיות ה Lean / Agile.

על קביעת העדיפויות

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

מצד שני, אם ה PBI הראשון ב backlog יהיה: “כמשתמש, אני רצה שבדיקת האיות תהיה מהירה, תוך כדי הקלדה” (= שיפורי ביצועים) – איך יוכל צוות הפיתוח לגשת למשימה? יהיה עליו לעשות שיפורי-ביצועים, אבל למה? למשהו שעוד לא קיים? למשהו שעוד לא ברור כיצד הוא יעבוד ומה הוא יעשה?!

זכרו את הקונטקסט: בסקראם, כולם נמצאים בריצה מספרינט לספרינט. ברוב הפעמים, לא יהיו הגדרות מדויקות של PBIs, ולא יהיו UI Mockups מוכנים אלא אם זה PBI שצפוי להתממש בספרינט הקרוב. כולם עובדים “Just-In-Time”.

הנה תוצאה אפשרית וסבירה למצב הנ”ל:

  • המפתחים יפתחו מנגנון Cache. כי cache = ביצועים.
  • כיוון שהם לא יודעים מה תהיה התנהגות הריצה של מנוע בדיקת האיות, ואלו תבניות התנהגות הוא יכתיב ל Cache – הם יוכלו לכתוב רק Cache “גנרי”. כזה שעושה הכל “בסדר”, אך לא מצטיין באף תסריט ספציפי.
  • סביר אפילו, שעבור הרגשת הביטחון שה Cache בסדר (“!Done means Done”) יפתחו אותו קצת אקסטרה – רק כדי “להרגיש בטוחים”, וללא קשר לידע קונקרטי.
  • אם נקביל תהליך זה להתאמת מבנה-נתונים לבעיה, אזי על צוות הפיתוח לבחור מבנה נתונים לבעיה שלא הוגדרה. כיצד בוחרים מבנה נתונים כזה? מחפשים כנראה הרבה (O(1 – אולי HashTable שמאונדקס כפול ברשימה-משורשרת. ברור שזה מבנה נתונים שאינו “גרוע” למקרים רבים – אך כנראה שגם לא אופטימלי לכמעט אף מקרה.

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

כלומר: ה PO יכול “לעשות הכל ע“פ הספר” – ועדיין להגיע לתוצאה לא-טובה.

“Amplify Learning / Amplify Feedback”

אם נחזור לעקרונות ה Lean, ניזכר שאחד מעקרונות היסוד הוא “הגבר את הלמידה” – זהו עקרון מרכזי בעולם האג’ייל[ג].

בעת בניית וסידור ה Backlog, על ה PO לחבוש את “קסדת בוב-הבנאי” ולקחת בחשבון גם שיקולים של בניית מוצר: מה הגיוני לבנות לפני מה? היכן ישנן שאלות מהותיות (טכנולוגיות) פתוחות – שאם נתקוף אותן קודם לכן נוכל לייצב טוב יותר את ארכיטקטורת המערכת.
תזכורת: ב”מפל המים”, החופש המלא לקבוע את סדר הפיתוח היה בידי צוות הפיתוח. מנהל המוצר העביר “מסמך דרישות” והוא נפגש עם הצוות לאחר שנה לראות את התוצאות. צוות הפיתוח היה מודע לאספקטים הטכנולוגיים ויכול היה לקבוע סדר הגיוני של פיתוח.
לרוע המזל, שיטת מפל המיים, דווקא בנקודה זו, לא עבדה כ”כ יפה: כמעט תמיד גוף הפיתוח לא סיים לממש את כל הדרישות ולעתים נוצר מצב בו פ’יצר משני פותח במלואו, בעוד פ’יצר מרכזי וחשוב – נשאר בחוץ.

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

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

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

מי שבא מרקע של מדעי המחשב בוודאי זוכר את האלגוריתמים לסריקת גרף: BFS ו DFS. בכדי “לגדל מערכת” באג’ייל אנו נרצה בבירור לעשות DFS (להגיע ל”משהו עובד” מהר ככל האפשר) ולא BFS (לעבוד שכבה שכבה ולסיים עם כולן בו-זמנית).

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

כיצד נראה Flow מינימלי ועובד? – הכי פשוט שאפשר.
בדוגמת האיות, אפשר להגדיר מילון סתמי שאומר:”Ncok ו Rokk הן שגיאות כתיב, כל השאר – בסדר”. במקום להתעסק בהגדרת מילונים ובנייתם – אנו יכולים להתקדם לשלב הבא. בניית מילון היא משימה צפויה / ברורה. עלינו לזהות משימות צפויות או ברורות ולדחות אותן לשלב מאוחר יותר.

בשלב הבא: לדאוג שהמוצר שלנו (“אקסל”) מאתר את שגיאות הכתיב תוך כדי הקלדה ומציג אותן למשתמש בצורה שאנו מוצאים כטובה. ה UI יכול להיות מאוד בסיסי.
ברגע שיש לנו את הסיפור השלם והבסיסי ביותר עובד (עם כמה הנחות מקלות בדרך – שנחזור ונתקן) – השגנו משהו חשוב מאוד:
שיתפנו את כולם בצורה מאוד ברורה בחזון שלנו. מצגות ומסמכי-מלל הם דבר נחמד, אבל רק כאשר מתחילים לחבר את הפרטים – התמונה באמת מתבהרת. מתכנתים זקוקים לפרטים על מנת לבנות את המערכת בצורה נכונה. גם מנהל המוצר עצמו יוכל לוודא יותר טוב שהוא בכיוון הנכון על בסיס משהו מוחשי וברור – פנימית וחיצונית לארגון.
בשלב מאוחר קצת יותר, כשהמפתחים יגיעו לממש את הסיפור “בדיקת איות תוך כדי הקלדה” – תהיה להם הבנה מספיק טובה של התסריט – על מנת לבנות את המנגנון הנכון להאיץ אותו.
הנה, כך אנו רוצים שבדיקת האיות תראה. אופס – וצריך גם “ignore”. האם אתם רוצים לגלות זאת לפני, או אחרי שכתבתם את כל הקוד?


הגדירו Maximum Learning PBIs – ויישמו אותם ראשונים
ה PBIs מהם נלמד הכי הרבה הם לרוב כאלו שכוללים UI.

מדוע? “תמונה אחת שווה אלף מילים”. כבני אדם, אנו יכולים להפיק מהגדרות ה UI הבנה טובה יותר של הדרישה מאשר מהתיאור המילולי שלה. אפילו ה PO בעצמו יתגבש טוב יותר – אם הוא יעבוד עם איש ה UX בשלב מוקדם להגדיר את ה User Interaction. בעיות רבות עלולות לצוף באופן זה בשלב מוקדם.
במערכות ללא UI, כגון שרתים – יהיו אלו הנתונים. “נתונים לפני” ו “נתונים אחרי” או “מה נכנס” מול “מה יצא”. התבוננות בנתונים ראליים, בעלי משמעות – שווה יותר מעשרות מצגות ופגישות הדנות ב”כוונות” וב”המוצר – לאן?”. פשוט אספו נתונים ראליים שאתם רוצים שהמערכת שלכם תעבד והציגו אותם.ב”מפל המים” לימדו אותנו שאי אפשר לכתוב UI לפני שהשירותים שמאחוריו כבר כתובים ועובדים. בואו נתקן אמירה זו: לא ניתן לסיים לכתוב את ה UI לפני שסיימנו לכתוב את השירותים שמאחוריו, אבל אפשר להתחיל.
UI שבו רוב הכפתורים הם disabled – הוא התחלה סבירה על מנת להגביר את הלמידה. כיתבו קוד שמציג התנהגויות מרכזיות שהוגדרו מראש, אפילו בצורה hardcoded, ועליתם מדרגה ברמת הלמידה שניתן להשיג בשלב מוקדם זה.

  • UI יוצר דיון וגורם לאנשים להביע דעה / להבין / להעלות בעיות.
  • ה UI ישתנה בכל מקרה – עדיף להתחיל את סבב השינויים עוד לפני שסיימנו לכתוב את כל המערכת שתומכת בו.
  • UI הוא “הקשר עם הלקוח/משתמש”. קשר שממנו כל ההתנהגויות האחרות נובעות. שינינו את החוזה מול המשתמש? סביר להניח שכל המערכת תושפע מכך.

הערה: כל אלה נכונים, באין UI, גם לנתונים איכותיים.

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

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

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

  1. אנו עובדים תחת הנחות מסורתיות של “מפל המים” / BFS: קודם נסיים את התשתיות – ואז נגיע בסוף ל UI.
  2. חתירה, מרבית, ל End 2 End flow – שגם מתחילה מהUI.
התרגיל הוא דמיוני, אך אני מרגיש שהוא מתאר יפה את הדינמיקה המציאותית.
הוא ממחיש כיצד עבודה ממוקדת תוצאה-מוקדמת יכולה למנוע מאיתנו להשקיע בכתיבת קוד שלא יהיה לבסוף בשימוש.
יש גם יתרון מובנה בלסיים “Flow” מוקדם יותר: יש זמן יותר לבחון את ה Flow, לתקן בו בעיות או להציע בו שיפורים.את ההשפעה החיובית של גישה זו על הארכיטקטורה – התרגיל המחשבתי הזה לא מפגין. תאלצו לנסות בעצמכם את שתי הגישות בכדי להיווכח בהבדל.

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

לחצו על התמונות להגדלה.

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

—-

[ב] Product Backlog Item – יחידת עבודה לספרינט עבור הצוות או חלקים ממנו.

[ג] תוכלו לקרוא על עקרון זה ואחרים בפוסט “קיצור תולדות הסקראם – חלק 2” בסדרה זו.

4 כללים למדידת פשטות של תוכנה

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

אחת ההגדרות שאני אוהב היא של בחור בשם J.B. Rainsberger, הגדרה של ״מבנה פשוט של תוכנה״.

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

ע״פ ריינסברגר (עוד), ישנם ארבעה אלמנטים (עם סדר עדיפויות ברור) המובילים לתוכנה פשוטה:

  1. כל הבדיקות עוברות. 
  2. צמצום כפילויות קוד. 
  3. הקוד מתאר כוונה (clarity). 
  4. צמצום  מספר האלמנטים בקוד למינימום האפשרי  אלמנטים שאינם משרתים את מטרות 1-3.

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

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

הנה דוגמה של קוד \”ללא כפילויות\” שהפריע לי והפכתי לכפול:

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

הנה הקוד לאחר השינוי:

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

קוד המתאר כוונה
אם מזקקים את הכלל של \”כתיבת קוד המתאר כוונה\”, לרוב עיקר העבודה היא מסביב לשמות: שמות של פונקציות או משתנים, או שמות חדשים הנוספים ע\”י פעולות \”extract method\” או \”introduce variable\”.

ישנן 4 \”דרגות\” של שם:

  1. שם סתמי
  2. שם נכון
  3. שם מדויק
  4. שם בעל משמעות (\”meaningful\”).
עצלנות ולחץ גורמים לנו להיצמד לתחתית הסקאלה (1,2), בעוד הקפדה ומקצועיות דוחפים אותנו לראש הסקאלה (3,4).
מאוד נהניתי, כשקראתי את ריינסברגר, כשהוא מתאר בדיוק רב הרגל שגם אני רכשתי: כאשר אני רואה קוד כפול אני מבצע extract method לשורות הכפולות, גם מבלי להבין לעומק מה המשמעות שלהן. פעולה טכנית גרידא.
אני נותן לפונקציה שנוצרה את השם \”foo\”. מבלי לחשוב. תוך כדי עבודה אני מבין מה הפונקציה עושה ומשנה את שמה. לאחר 10 דקות עבודה ייתכן והשם שונה כבר שלוש או ארבע פעמים, אך אני מרגיש בבירור שאלו עליות דרגה ברמה של השם. לעתים פשוט צריך לנסות איזה שם ו\”לחיות\” איתו כמה דקות על מנת למצוא שם מוצלח יותר.
צמצום אלמנטים שאינם משרתים את מטרות 1-3. 
למי שנתקל ברעיונות אג\’יליים – אני מניח שעקרון זה הוא מובן מאליו: Eliminate Waste. עדכון המטרה בעיקרון זה היא להימנע ממרכיבים בקוד שלא משרתים את הפונקציונליות, לא מונעים קוד-כפול ולא מסבירים את התנהגות המערכת, כל מיני \”הכנות למזגן\”[ג].

מי שעובד ב (Test Driven Development (TDD נהנה באופן מובנה מעקרון 1 (\”כל הבדיקות עוברות\”) ועקרון 4 (\”צמצום אלמנטים לא-חיוניים\”). זו הדרך המהירה ליצירת קוד פשוט, שגם יישאר פשוט לאורך זמן.
מכאן נותרו רק 2 פעולות לשים לב אליהן:
צמצום כפילויות קוד [ב] ו כתיבת קוד המתאר כוונה / קוד ברור. המשיכו לעשות את אלו בצורה תמידית – והמבנה של הקוד שלכם, או ה \”design\” של הקוד שלכם – יהיה פשוט. זו הדרך היעילה ביותר שאני מכיר.
זה אולי נשמע קצת פשוט מדי: אולי ציפיתם לשימוש במחשבון של Cyclomatic complexity וטכניקות של ספירת Weighted Micro Function Points (ממש כמו ספירת קלוריות). 
צר לי לאכזב אתכם: כמה אנשים טובים השקיעו שנים ביצירת מודלים מתמטיים לתיאור סיבוכיות של תוכנה – אך בפועל כמה עקרונות פשוטים הם אלו שיגרמו לקוד שלכם להיות פשוט יותר, וקל יותר לתחזוקה.

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

—-

[א] יש המשתמשים במילה Design על מנת לתאר מצב של תוכנה חיה, ולא רק את התוכניות. לדוגמה: \”Refactoring: Improving the Design of Existing Code\”. אם הרעיון הזה ברור לכם – הרגישו חופשיים להשתמש במילה design ולא ב structure.

[ב] אזהרה!: יש המבלבלים בין צמצום כפילות קוד בתוך המערכת, לבין שימוש-חוזר בקוד (code re-usability) או \”צמצום כפילות קוד בעולם האנושי\”. בעוד ביטול כפילות-קוד בתוך הקוד שאתם כתבתם הוא כמעט-תמיד דבר טוב, שימוש חוזר בקוד (\”חיצוני\”) הוא נושא מורכב עם הרבה ייתרונות וחסרונות. אני מתכנן לעסוק בנושא זה לעומק בפוסט נפרד.

[ג] המטאפורה הזו מטעה! מזגן הוא דבר מוכר שאנו יודעים לצפות לו ולבנות לו הכנה טובה. רוב ה\”הכנות למזגן\” בתוכנה הם ניחושים בעלטה אודות משהו לא-ידוע. בהתאמה: מנסיוני, רובן מתגלות כלא-יעילות או חסרות שימוש.

על בדיקות-יחידה (Unit Testing)

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

מי שניסה Unit Tests מוצלח עלול פשוט להתאהב! כנראה שמעבר לתמורה המעשית – זוהי חוויה רגשית. אחרת, אינני יודע להסביר מדוע אנשי מפתח בעולם התוכנה מתאמצים להוכיח שכתיבת Unit Tests בעצם חוסכת בעלויות הפיתוח[א], מעלה את האיכות, משמחת את הלקוח ומרצה את בעלי המניות… . נראה שהם מאמינים מושבעים המנסים רק להוכיח את אמונתם. מי שחווה את הנירוונה של הרצה של 100+ בדיקות שמסתיימות כולן בהצלחה אחרי ביצוע שינוי מהותי בקוד – מבין את ההרגשה הממכרת. הצבע הירוק זורם ומפיח חיים בעץ הבדיקות שלכם. החרדה מהלא-ברור מתחלפת בשלווה מרגיעה שזורמת בעורקיכם ומפיגה כל זכר לקושי או מתח…

שנייה… להתאהב?! שלווה? נירוונה? – “על מה אתה מדבר?!”
אני יכול להעלות בזיכרוני תמונות של עשרות פרצופים סובלים וממורמרים בעקבות “איזו החלטת הנהלה לכתוב Unit Tests”. אז תנו לי להסביר: פעמים רבות ראיתי ניסיונות לאימוץ Unit Tests שגרמו סבל רב למפתחים ו/או לא החזיקו מעמד זמן רב. הייתי אומר שנתקלתי ב 2-3 ניסיונות כושלים לאמץ Unit Tests על כל ניסיון מוצלח.

יש עוד זן של אימוץ Unit Tests שאינני יודע אם להגדיר כהצלחה או כישלון. אקרא לזן זה “מטופש, אבל אופטימי”. מדובר בקבוצת מפתחים שמשקיעים המון זמן בכתיבת בדיקות-יחידה מסורבלות וכמעט חסרות משמעות – אך הם מרוצים. המורל גבוה והצוות מעיד על עצמו “מאז שהתחלנו לכתוב בדיקות האיכות נסקה וקצב הפיתוח הואץ. זה עובדדדדדד!”.
מה אני אגיד? פלסיבו. האם לוקחים מחולה פלסיבו שיש לו השפעה?!

מה הן בעצם בדיקות-יחידה (Unit Tests)?

[מינוח: “קוד פעיל” = productive code. זה שעושה את העבודה.]

בדיקות יחידה הן:

  • מקבילות לקוד הפעיל. לכל חתיכת קוד פעיל יש חתיכה “חברה” של קוד בדיקה שבודק אותו.
  • נכתבת ע”י המפתח שכתב גם את הקוד הפעיל.
  • רצות כל הזמן, תוך כדי פיתוח, כמו “קומפיילר שני”.
  • בהערכה גסה, ניתן לצפות לשורת קוד של בדיקות לכל שורת קוד של קוד פעיל.
  • דורשות תחזוקה. ימצאו בהן באגים.
  • דורשות שהקוד הפעיל ייכתב בגישה קצת אחרת: “Testable Code”.
  • רצות מהר. ממש מהר[ב].
  • דורשות מאסה קריטית (של כיסוי הקוד הפעיל, לפחות באזור מסוים) – על מנת להיות יעילות.
  • “בונות” אצל המפתחים ביטחון אמיתי בנכונותו של הקוד הפעיל.
  • משפרות איכות פנימית.
  • מובילות אותנו לכתוב קוד מודולרי וברור יותר. בזמן כתיבת הבדיקה, אנו הופכים לרגע מ”יצרני הקוד הפעיל” ל”צרכני הקוד הפעיל” – מה שעוזר מאוד להבחין בקלות השימוש בממשקים / API.
  • משרתות אותנו כתיעוד מעודכן ורב-עצמה.
ההבדל בין בדיקות יחידה שמחזיקות את ההשקעה לבין אלו שלא. שימו לב: לעתים נרצה בדיקות יחידה גם אם הן עולות לנו יותר. מקור:  http://xunitpatterns.com/Goals%20of%20Test%20Automation.html
כתיבה ותחזוקה של בדיקות-יחידה אכן גוזלות זמן, אך מצד שני הן חוסכות ומקצרות תהליכי פיתוח אחרים.
קוד לוגי מורכב יכול להיבדק ישר ב IDE. המתכנת יודע תוך שנייה אם הקוד עובד או לא. האלטרנטיבה, ללא קיומן של בדיקות-יחידה, היא לבצע Deploy של הקוד, להגיע למצב הרצוי במערכת ורק אז לבדוק אם התוצאה היא הרצויה – תהליך ארוך בהרבה.
היכולת לבצע refactoring בביטחון ובמהירות עם סיכוי נמוך לתקלות – גם הוא זרז משמעותי בתהליך הפיתוח. במיוחד בפרוייקטים גדולים בהם אינני יכול להכיר את כל הקוד.ראיתי שני אנשים שישבו ביחד וכתבו אלגוריתם אך הסתבכו: כל רגע מקרה אחר לא עבד. הנטייה הייתה לא לכתוב קוד בדיקות עד שהאלגוריתם לא עובד, “למה לכתוב פעמיים?”. דווקא בכך שהתחילו לכתוב בדיקות תהליך הכתיבה הסתיים מהר יותר: אדם אחד שכתב בדיקות הצליח לסיים את המשימה מהר יותר מאלו שלא. הכל בזכות faster feedback cycle.

בראיון העבודה שלי כמתכנת ב SAP היה לי תרגיל תכנותי באורך שעתיים, תנאי חשוב היה “אפס באגים”. היה לי ברור מה לעשות. לאחר שעה שבאו לבדוק את התקדמותי הראתי “יש לי x בדיקות, חלק קטן כבר עובר”. אני זוכר את ראש הצוות שהביטה בי במבט המום, לא מבינה כיצד בראיון עבודה, לחוץ בזמן, אני כותב בדיקות-יחידה. היא לא הבינה שכתבתי בדיקות בגלל הלחץ. זו הייתה הדרך הקצת יותר ארוכה מבחינתי – אך זו שהצליחה להרגיע אותי ולהשאיר אותי ממוקד. סיימתי את המבחן בזמן וללא באגים שנמצאו – שני אירועים שהסתבר שהיו דיי נדירים במבחן ההוא…

בדיקות יחידה הן לא:
  • נכתבות ע”י אנשי QA, מפתחים צעירים או סטודנטים.
  • כלי יעיל לשיפור איכות חיצונית.
  • נזרקות לאחר שהקוד הפעיל “משוחרר” (shipped).
  • קוד שקל במיוחד לכתיבה.
  • מיוצרות ע”י כלים אוטומטיים (generated). יש להפעיל לא-מעט חשיבה אנושית בריאה על מנת לכתוב בדיקות יחידה טובות.
  • בדיקות אינטגרציה / מערכת / רכיב  component test / פונציונליות functional test / או API test.

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

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

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

האם בדיקות היחידה שלכם מתנהגות יותר כמו בדיקות פונקציונליות? – סימן שיש לכם בדיקות פונקציונליות ולא בדיקות יחידה. עצם זה שאתם משתמשים ב qUnit / nUnit / jUnit לא אומר שהבדיקות שנכתבות הן אכן בדיקות יחידה!

הערה: איני מנסה לטעון ש”בדיקות-יחידה הן טובות” או “בדיקות פונקציונליות הן פחות טובות”. פתרון אוטומציה מאוזן כולל לרוב כ 70% בדיקות יחידה, כ 20% בדיקות פונקציונליות וכ 10% בדיקות ל UI. הבדיקות השונות משלימות זו את זו.

מה בודקים בבדיקות יחידה?

אפשר לחלק את הקוד הפעיל ל3 אזורים:

קוד “נטול לוגיקה”
לדוגמה: getters או setters (שלא משנים את הערך), כתיבה ללוגים או השמות של ערכים מאובייקט אחד לאובייקט שני.
הסיכוי שקוד שכזה ישונה ויישבר במהלך חיי המוצר הוא לא גבוה. יתרה מכך: דמיינו כיצד תראה בדיקה של קוד שכזה:

  1. קבע את X להיות 4.
  2. השם את X.
  3. קרא את X והשווה שהוא 4.

קוד הבדיקה הוא בעצם השתקפות של הקוד הפעיל. אם קראנו את הקוד הפעיל והוא נראה לנו תקין – לא סביר שקריאה בקוד הבדיקה תגלה לנו תובנה חדשה. גרוע מכך: סביר שהבסיס לקוד הבדיקה הוא בעצם copy-paste של הקוד הפעיל.
מסקנה: התמורה להשקעה בבדיקות קוד “נטול לוגיקה” היא קטנה ביותר ומומלץ לא לבדוק סוג כזה של קוד.

קוד אינטגרציה
קוד זה הוא קוד Gluing שמחבר בין רכיבים / מערכות:

  1. עשה א’
  2. עשה ב’
  3. עשה ג’
במקרים אלו קוד הבדיקה יכלול כנראה mock על מנת להקשיב להתנהגות הקוד והבדיקה תבדוק שא’, ב’ וג’ אכן נקראו. אולי אפילו שהם נקראו לפי הסדר. קוד הבדיקה עלול להיות ארוך משמעותית מהקוד הפעיל. אם הפעולות המדוברות שייכות לאובייקטים אחרים – אדרבא. לעתים קרובות קוד האינטגרציה יכלול אפילו קריאות למערכות אחרות / קוד שלא באחריותנו. עלינו לכתוב Mock Object ועוד Mock Object… בשביל מה כל זה? בשביל לבדוק לוגיקה דיי פשוטה של קריאה לסט פעולות ע”פ הסדר?
למרות שקוד זה רגיש יותר לשבירה במהלך חיי המערכת (תכונה שגורמת לנו לרצות ולבדוק אותו), כל שינוי שלו ידרוש מיד שינוי של הבדיקה. קוד הבדיקה הוא השתקפות (שמנה) של הקוד הפעיל.
מסקנה: ההשקעה בבדיקת קוד שכזה לא משתלמת מבחינת ההשקעה. אולי במערכת שבהן האיכות מאוד חשובה – משתלם להשקיע בבדיקות של קוד אינטגרציה. באופן כללי: התועלת להשקעה – נמוכה.

קוד לוגי (Business Logic)
קוד זה מתאפיין באזורי קוד שהם מופיעות פקודות if ו for (או המקבילות בשפה בהם אתם עובדים) – conditional logic. הקוד לוגי הוא הקוד הרגיש ביותר ל”שבירה”. בעצם: לעתים רבות הוא לא כתוב כשורה מלכתחילה!

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

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

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

function FormValidator($form) {

  var username = $form.find('#username'),
      email    = $form.find('#email'),
      error    = $form.find('.error');



  $form.bind('submit', function() {


    if (username.val() === 'Wizard') {
      error.html('Your argument is invalid');
      return false; // prevents the submission of the form
    }
    else if (username.val() !== 'Harry') {
      error.html('Your name is invalid');
      return false;
    }
    else if (!/@/.test(email.val())) {
      error.html('Your email is invalid');
      return false;
    }


  });
};

בפונקציה ניתן לזהות מספר רב של משפטי if, מה שמעיד על כך שיש כאן קוד לוגי – קוד שאנו רוצים לבדוק. גם regular expression (שהתחביר שלו בג’אווהסקריפט הוא טקסט בין סוגרי “/” כמו בביטוי האחרון) הוא קוד לוגי. ביטוי ה regex מבטא תיאור conditional logic כללית שנוכל להזין לה כמה דוגמאות ולוודא שהתוצאה המתקבלת היא הרצויה.

כדי להפעיל את הפונקציה אנו זקוקים להעביר ארגומנט בשם form$ (אובייקט jQuery) שנוצר ממציאת אלמנט form ב HTML שמכיל תגיות (כנראה מסוג input) עם מזהים בשם username וemail. את התוצאה של הרצת הפונקציה נוכל לקרוא מתוך שדה ה error (כמה נוח) בתוך אלמנט ה form.

יש לנו כמה בעיות:

  • הפונקציה החיצונית תרוץ, אך הפונקציות הפנימיות לא יפעלו ללא לחיצה של המשתמש על כפתור “Submit”. אולי אפשר “לזייף” לחיצה ב javaScript?
  • ה errors הם text string למשתמש שיכולים להשתנות בקלות. כל שינוי ישבור את הבדיקה => בדיקה רגישה לשינויים.
  • טעינת ה html markup (דף HTML עם javaScript נלווים) עלולה לקחת זמן רב. כלל שהבדיקה אטית יותר – יריצו אותה פחות. בבדיקות JavaScript מקובל להפריד את הבדיקות לקובצי html נפרדים ולהריץ רק אותם – אך עדיין יש פה עבודה לכתוב ולתחזק את ה makrup.
בכתיבת קוד בדיקות בג’אווהסקריפט מקובל לכתוב את בדיקת-היחידה בקובץ html נפרד. ניתן לכתוב קוד שיפעיל פעולת submit ויתפוס את ה error שנזרק. התוצאה: אנו כותבים ומשקיעים יותר שורות קוד וזמן בבדיקה מאשר בקוד הפעיל. לא סימן טוב.
במקרים אחרים, פחות ידידותיים (למשל אסינכרוניות, הסתמכות על סביבה חיצונית כמו טעינת image), כתיבת הבדיקות עלולה להיות פרויקט של ממש. אנו רוצים להימנע מכך.
הקוד למעלה הוא בעצם קוד מעורבב: קוד אינטגרציה וקוד לוגי. אם בבדיקות יחידה עסקנינו, עלינו ללמוד להפריד חלב (אינטגרציה) ואוויר (קוד נטול-לוגיקה) מבשר (קוד לוגי) – ולהתמקד בבדיקות בבשר בלבד. במערכת קיימת ההפרדה היא מאמץ – דבר שמקשה מאוד על ההוספה של בדיקות-יחידה ומפחית את יחס העלות-תועלת. לאחר שביצענו את התרגיל הזה כמה פעמים, יהיה לנו טבעי לכתוב את הקוד מופרד מלכתחילה – כך שכתיבת קוד חדש ובדיק (testable) לא תגזול זמן רב יותר. קוד בדיק הוא גם קוד מודולרי ונקי יותר – כך בעצם אנו מרוויחם פעמיים!

הנה הקוד לאחר שעשינו לו refactoring ובודדנו את הקוד הלוגי – ללא תלות ב DOM:

FormValidator.validate = function(username, email) {

  var errors = [];

  if (username === 'Wizard')
    errors.push('Your argument is invalid');

  else if (username !== 'Harry')
    errors.push('Your name is invalid');

  else if (!/@/.test(email))
    errors.push('Your email is invalid');

  return errors;

}

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

  • החזרת רק קוד ה error והפיכתו לטקסט מאוחר יותר.
  • קבלת כל אלמנטי ה input בטופס כמערך בתור פרמטר לפונקציה – כך שלא יהיה צריך לשנות את החתימה שלה (ושל הבדיקות) בכל פעם שנוסף לטופס פרמטר.

כלי עבודה

  • xUnit – ספרית בדיקה לשפה שלכם כגון nUnit ל #C או jUnit לג’אווה.
  • (CI (continuous integration על מנת להריץ את הבדיקות כל הזמן ולדעת מוקדם ככל האפשר – כשמשהו נשבר.
  • Code Coverage Tool – על מנת לדעת כמה מהקוד שלכם עובר בדיקה. למשל Emma ל Java או NCover ל #C.

אם אתם עובדים עם Java ו Jenkins (כלי CI) יש תוספת מאוד נחמדה בשם Sonar שעושה גם Code Coverage בעזרת Emma ומאפשרת ביצוע Drill Down שימושי למדי.

על מדד ה Code Coverage
אחד הדברים שמנהלים אוהבים לעשות הוא להציב לצוות יעדים מספריים. (Code Coverage (CC הוא מדד מספרי ברור וגלוי (אם הוא משתקף באיזה דו”ח שקל להגיע אליו) המתאר את כמות הקוד הפעיל שמכוסה ע”י בדיקות-היחידה. החישוב הוא % שורות הקוד הפעיל שהופעלו בעת הרצת כל בדיקות היחידה הקיימות אצלנו.

בעבר פיתחתי רגשות שליליים עזים למדד ה Code Coverage. הייתי חבר בפרוייקט בו נדרשנו ל 100% כיסוי – לא פחות. המערכת הייתה מערכת P2P Video Streaming שהייתה כתובה חציה ב Java/AspectJ וחצייה ב ++C. בקוד היו חלקים גדולים שהתנהגו בצורה סינכרונית, והיו פנייות רבות לפעולות I/O (לרשת). על מנת לבדוק חלקים מסויימים בקוד נאלצנו לכתוב את הבדיקות עצמן בצורה אסינכרונית: השתמשנו בספריית ה Concurrency של Doug Lea בעיקר עבור קוד הבדיקות – ורק מעט עבור הקוד הפעיל. זה היה בשנת 2003 ומקורות הידע בבדיקות-היחידה היה מצומצם.

הקזנו נהרות של דם בכדי להעלות למעל 90% CC. הכי גבוה שהגענו היה 93%-לרגע, כך נדמה לי.
תכונה בולטת של CC הוא המאמץ השולי הגובר: להשיג 50% זה יחסית קל. כל אחוז מעל 80% הוא עבודה מתישה וקשה – ובכל רגע נתון מישהו יכול לבצע submit של קוד לא-בדוק ו”לזרוק” את הצוות אחוז או שניים אחורה.
על האחוזים מעל 90% אין לי מה לדבר. זה נראה כמו טעות חישוב – כי לא נראה היה שאפשר באמת להגיע לשם. הוספנו לקוד הפעיל getters[ג] רק כדי “לסחוט” עוד קוד קל-לבדיקה ולשפר אחוזים. הערכנו שאנו משקיעים פי 3 זמן על קוד בדיקות מאשר על קוד פעיל (מצב לא בריא בעליל, אני יודע כיום לומר). מאז ראיתי פרוייקט שבו השקיעו כשליש מהזמן על כתיבת בדיקות, והחזיקו ב CC של כ 80% באופן שוטף.

כמה מסקנות:

  • התמודדות לא מוצלחת בכתיבת בדיקות-יחידה עלולה לגרום למפח נפש רציני.
  • היעד הסביר, לדעתי כיום, ל CC תלוי מאוד בסוג המערכת:
    בקוד שרובו אינטגרציה / UI / IO אני חושב שבריא לשאוף ל 60-70% CC.
    בקוד שרובו parsing או לוגיקה טהורה (לדוגמה XML Parser) אפשר בהחלט לשאוף ל 80-90%.
    במערכת שהיא באמצע (כמו רוב המערכות) אני חושב שיעד בריא הוא 70-80% CC. אני מציין טווח ולא מספר מדויק כי CC הוא מדד “חי”. בכל Submit של קוד הוא ישתנה. להישאר כל הזמן בטווח של 10% – הוא משהו שאפשר לדרוש מצוות. במיוחד במערכת גדולה.
  • CC הוא תנאי מחייב אך לא מספק. אם יש לכם 20% CC – בטוח שהקוד שלכם לא בדוק טוב. אם יש 80% CC – אזי זה סימן טוב אבל יש לעבור על הבדיקות ולוודא שהן משמעותיות. ניתן בקלות להשיג CC גבוה עם בדיקות חלקיות ולא יעילות.
  • למרות שבתאוריה ב TDD אמור להיות 100% CC, תאוריה זו תיאוריה: כמעט תמיד יהיו חלקים של חוק אינטגרציה, Adapters למשל, שלא נכון או כדאי לבדוק אותם.

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

(TDD (Test Driven Development
TDD הוא סוג של “השלב הבא” בבדיקות-יחידה. הרעיון הוצג ע”י קנט בק – גורו וחדשן אמיתי בעולם התוכנה.

ראיתי מספר פרשנויות מוזרות שמתהדרות בשם “TDD”. למשל: צוות שכל תחילת ספרינט השקיע 2-3 ימים בכתיבת מסמכי בדיקה מפורטים בוורד, כתב את הקוד ואז בסוף הספרינט מימש את הבדיקות בקוד, שבכלל היו בדיקות פונקציונליות / מערכת.
אם אתם רוצים להתהדר ב TDD או אפילו ATTD (נשמע טוב!) – לכו על זה. שום חוק מדינה לא יאסור על מאלף-סוסים, נאמר, להתהדר בכך שהוא “עובד TDD”, אז בטח לא על מישהו שעוסק בתוכנה…

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

  • איך אתם יודעים שתצליחו לכתוב קוד בדיק (testable) ולא תאלצו לעשות בו שינויים אח”כ? – פשוט כתבו כל בדיקה בסמיכות רבה לקוד שהיא בודקת. אולי אפילו כיתבו את הבדיקה לפני.
  • איך תוודאו שהכיסוי שלכם של בדיקות הוא אופטימלי? – החליטו שקוד פעיל נכתב רק על מנת לגרום לבדיקה לרוץ בהצלחה. “מה שלא בדוק – לא קיים“.
  • איך תפחיתו באגים בבדיקות שלכם – ופרט את המקרים של בדיקות שעוברות בהצלחה גם כאשר הקוד לא עובד כשורה? כתבו והריצו את הבדיקות קודם לכתיבת הקוד וודאו שהן נכשלות.
כלומר: קודם כותבים את קוד הבדיקה, ורק לאחר מכן – את הקוד הפעיל.
TDD הוא הפעלה חוזרת של סדר הפעולות הבא:
  1. הבינו מה הפונקציונליות (קטנה) שאתם רוצים להוסיף למערכת.
  2. כתבו בדיקת-יחידה שבודקת את הפונקציונליות הזו, קצה-אל-קצה.
  3. הריצו את הבדיקה: עליה בהכרח להיכשל, מכיוון שהפונקציונליות לא כתובה עדיין![ד]
  4. כתבו פיסת קוד הפשוטה ביותר שאפשר שתעביר בדיקה בודדת[ה]. אם הריצה הצליחה המשיכו לחתיכה הבאה וכו’.
  5. לאחר שכל הבדיקות ירוקות (עברו בהצלחה) – המשיכו לפונקציונליות הבאה.
הסבבים ב TDD מאוד קצרים: כתיבה – הרצת בדיקות, כתיבה – הרצת בדיקות.
המתכנת מחליף תדיר 2 כובעים: כותב הקוד הפעיל וכותב הבדיקות, והוא אמור לנסות בכל כובע “להערים” על הטיפוס השני ולספק לו את המינימום האפשרי. כלומר: להערים על עצמו.
אני לא בטוח שאפשר להסביר בקלות את סגנון העבודה מבלי לבצע תרגילים בפועל. יש בו אלמנט של משחק – שבהחלט עשוי לגרום להנאה.
בפועל TDD מסייע:
  • להתמקד במטרה ולכתוב רק קוד שנדרש (“eliminate waste”)
  • להשיג בדרך הקצרה והפשוטה קוד בדוק עם Code Coverage גבוה.
  • לכתוב את ה API / interface של כל מחלקה בראייה של הלקוח (= הבדיקה) – דבר שמסייע ליצירת API טובים וברורים.
  • מכריח אתכם לכתוב קוד מודולרי ובדיק (testable).
הסיבה העיקרית, לדעתי, ש TDD הוא לא כל-כך נפוץ היא שחלק גדול מאלו ש”עובדים עם בדיקות-יחידה” בעצם עובדים על עצמם בעיניים – הם כותבים בדיקות פונקציונליות או בדיקות בכיסוי מזערי. ברגע שיש לכם תהליך של כתיבת בדיקות-יחידה שעובד, המעבר ל TDD הוא השלב הטבעי הבא.

סיכום 

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

  • הבדיקות נכשלות תדיר – גם כשהקוד הפעיל עובד בסדר => בדיקות רגישות לשינויים (fragile tests).
  • הבדיקות שלכם בודקות קוד שלא סביר שיישבר (למשל קוד מה JDK)
  • הבדיקות שלכם נכשלות בהמוניהן – כמו בדיקות פונקציונליות.
  • אתם עושים שימוש תדיר ורב ב Mock Objects (שהם בעצם סוג של patch לקוד קשה-לבדיקה).
  • יש לכם בדיקות שמשאירות “עקבות” לאחר שרצו. זו יכולה להיות בעיה או בבדיקות או בקוד הפעיל.
  • הבדיקות שלכם רצות לאט.
כמה מדדים לשימוש בריא בבדיקות-יחידה:
  • הבדיקות רצות מהר.
  • אתם מריצים את הבדיקות (“עץ ירוק”) גם כשלא נדרש. זה פשוט מרגיע אתכם.
  • כאשר אתם נכנסים לקוד חדש, הדרך הטבעית ביותר היא לעבור ולקרוא את קוד הבדיקות.
  • בדיקות שנכשלות אכן תופסות באגים (רגרסיה או בכלל) לפני שאתם עושים submit.
  • כולם מתמכרים לרעיון של הבדיקות.

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

—-

[א] ע”פ התאוריה האקדמית, השקעה באיכות לרוב מעלה את עלויות הייצור – לא מוזילה אותן.

[ב] כאשר בדיקות יחידה מתחילות לרוץ לאט (נאמר, כפול מהקומפיילר או יותר) מחלקים אותן ל 2 חבילות: “בדיקות מהירות” שרצות כל הזמן, ו”בדיקות אטיות” שמריצים מדי פעם כמו ב CI או לפני פעולת submit של קוד.

[ג] שלפנים – בעברית. כמובן.

[ד] בלימוד TDD מלמדים לצפות לכישלון “אין כזו מחלקה”, אח”כ “אין כזו מתודה” ואח”כ – ערך לא נכון. ממש להתקדם בנאיביות. כשעובדים בפועל – מדלגים כמובן על השלבים הברורים-מאליהם.

[ה] בלימוד TDD מלמדים שאם אתם בודקים את הפונקציה (add(a,b והבדיקה הראשונה בודקת ש 2+2=4 – יהיה נכון לכתוב את גוף הפונקציה בצורה הכי מוכוונת-מטרה שאפשר, כלומר return 4. זאת על מנת לוודא שאינכם כותבים קוד מיותר (“מה שלא בדוק לא קיים”) אך גם להרגיל אתכם לכתוב בדיקות מספיק מקיפות שבודקות את הפונקציה במספר מספיק של מקרים.

ארכיטקטורה: Quality Attributes (חלק ב’ – כיצד משתמשים)

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

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

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

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

Security (נקרא במקור Integrity) – עד כמה מוגבלת / מאובטחת צריכה להיות הגישה לנתונים / שירותים של המערכת.

Efficiency – עד כמה יעילה המערכת בניצול משאבי החומרה העומדים לרשותה, או בניצול שירותים אחרים (כגון SOA) – והחומרה העומדת לרשותם.

Portability – היכולת של המערכת לרוץ בסביבות ריצה שונות, קרי מערכות הפעלה, סביבות ענן, תצורות רשת וכו’.

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

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

Maintainability – עלות תפעול (Administration / Operations) נמוכה של המערכת. מה שנקרא TCO (Total Cost of Ownership). מאפיין זה יכול להצביע על הוספת “פ’יצרים של ניהול המערכת”. עוד נושא שנכנס לקטגוריה זו היא קלות ההתקנה של המערכת, לעתים מתייחסים אליה כמאפיין איכות עצמאי: Installability.
דוגמה: כלי monitoring שיסייעו לנטר בעיות במערכת. השקעה ב Upgrade קל וכו’.

Developability (נקרא במקור Flexibility) – היכולת לפתח את המערכת בקלות, לבצע בה שינויים או לתקן באגים. מה שנקרא TCD ((Total Cost of Development מאפיין איכות זה עוזר לאפיין את הדילמה שבין איכות שמוסיפה סיבוכיות למערכת (Portability או Efficiency, למשל) למול היכולת לפתח מהר וביתר קלות.

Extensibility – היכולת להרחיב ולהוסיף יכולות חדשות למערכת, בעזרת Plug-in, API וכו’. כמובן שיכולת ההרחבה היא ליכולות / אזורים ספציפיים במערכת.

Supportability – היכולת לתמוך במוצר בקלות, אם מדובר בגוף המייצר את התוכנה או גוף שלישי. כלי Monitoring, לוגים קריאים, יכולת לבצע Dumps של זיכרון או נתונים, דו”ח על תצורת המערכת וכו’.

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

Testability – היכולת לבצע בדיקות למערכת. באופן אישי, מאפיין זה נראה לי כמו אמצעי להשגת Reliability או Developablity – ולא מטרה בפני עצמה.

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

Availability – מאפיין זה תקף בעיקר לשרתים (או שירותים) והוא מורכב מזמינות (אחוז הזמן שהשרת / שירות זמין ללקוחותיו) או תדירות הכישלונות (מה שנקרא MTBF). שרת יכול להיות זמין 99% מהזמן אך לכשול לשנייה אחת – כל יום, מה שיגרום לתקלות כואבות. (MTBF (Mean Time Between Failures עוזר להשלים את התמונה שנתון הזמינות לבדו לא מספק היטב.

רשימת “מאפייני איכות” ממקורות שונים.  מקור: http://www.clarrus.com/documents/Software%20Quality%20Attributes.pdf

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

Extensibility, Maintainability ו Supportability הם סוג של “פיצ’רים” שאולי מנהל המוצר לא ייזום ולכן על הארכיטקט להציע ולקדם. לכאורה נראה שהם שונים משאר מאפייני האיכות מכיוון שהם “פיצ’רים נוספים למערכת”. בפועל – השקעה בכל מאפייני איכות (למשל Availability או Safety) דורשת עבודה וההבדל בין שלושת מאפייני איכות אלו לשאר היא קטנה משנדמה במבט ראשון.

בחירת מאפייני איכות

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

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

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

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

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

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

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

במשך שנים, נושא חם בשוק ה IT היה בעיית ה Lock-In: בחרת ספק מסוים, לו יש API מסוימים. השתמשת בהם והיית מרוצה, אך ביום בו הספק מעלה מחירים / מחפף בשירות / קמים מתחרים עדיפים – “אין לכם שום דרך לבצע שינוי”.
האמירה של “אין שום דרך” היא אמירה לא מדויקת – אך מכיוון שהסיפור (אלמנטים טרגיים של בגידה?) הוא סיפור מוצלח – הוא הושרש והשאיר חותמו על אנשים רבים.

אחד ה “Best Practices” שנבעו מכך הוא לכתוב קוד “בלתי תלוי בספק (vendor)” בכלל, ו”בלתי תלוי בספק בסיס-הנתונים” בפרט. עבדתם עם מערכת אורקל ומחר אתם רוצים לעבור ל MS-SQL? אין בעיה – מחליפים רק את ה “connection string” – והכל עובד.

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

  • באף אחת מהמערכות הללו לא החליפו בסיס נתונים, עד היום (עד כמה שידוע לי).
  • הצוות הקפיד להשתמש ב ANSI SQL – וכך התעלם מרוב היכולות של בסיס הנתונים. אי-השימוש ביכולות הקשה בצורה משמעותית על הפיתוח ועל שיפורי ביצועים.
  • בכמה פעמים, המוצר נזנח / נכתב מחדש – אך בסיס הנתונים נותר. כלומר: בסיס הנתונים היה חלק יציב יותר מהקוד שהשתמש בו.

בפועל, ההחלטה המעשית הייתה לבחור במאפיין האיכות “no DB vendor lock-in”, על פני “Developability” ועל פני “Scalability”.

רק להבהיר: אני מדבר על מקרה בו אתם מוכרים Appliance או Hosting. כאשר הלקוח צריך לטפל בבסיס נתונים בעצמו, ברור למדי שלחברות רבות יש העדפה ברורה איזה בסיס נתונים הם רוצים להתקין: אולי יש להן DBA מוכשר שמכיר אותו, אולי הן כבר ביצעו השקעה מסוימת בטכנולוגיה (כלי ניהול, אבטחה וכו’) ספציפית לספק בסיס-נתונים זה.
מתן האופציה להתקין כל DB, עבור לקוחות רבים – יכולה להחשב כאיכות של Maintainability – כי אז הם יוכלו להתקין את בסיס הנתונים שהם מעדיפים.
הבחירה של פלטפורמות ג’אווה וNET. לבצע הפשטה שכזו (בעזרת ODBC ו JDBC) באה לתמוך במי שרוצה להשיג Maintainability.
אם כל מה שאני שומר בבסיס הנתונים היא טבלה או 2 של נתונים פשוטים – אין לי רווח רב משימוש בבסיס נתונים ספציפי.


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


דרך מומלצת 
על מנת להשתמש במאפייני איכות בצורה יעילה כדאי לבחור כמה תסריטים עיקריים במוצר ולהדגיש אותם:
“בתחום ה X אנו מעדיפים d על c ומעדיפים b על a”.אפילו כדאי לצרף דוגמאות ספציפיות, מה שנקרא “תסריט”:

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

Reusability

עוד תחום בו סקירת מאפייני האיכות יכול לסייע רבות הוא בהחלטה האם לעשות שימוש חוזר בקוד או “איחוד” של 2 מודולים בעלי פונקציונליות דומה.
לעתים רבות יש לנו 2 ספריות / מודולים שרשימות היכולות שלהן, כ checklist היא דומה או אולי זהה. לדוגמה: ספרייה לשליחת הודעות דוא”ל. שאלה מתבקשת היא “מדוע אנו מתחזקים 2 ספריות שעושות אותו הדבר? האם זה לא בזבוז?”
לפני שאתם רצים לאחד את הקוד לספרייה יחידה, כדאי לבחון מהם מאפייני האיכות של כל ספרייה.
אם ספרייה אחת מקפידה על reliability (מכיוון שהיא משמשת לשליחת התרעות מערכת חמורות), בעוד השנייה מתמקדת ה customizability (היכולת להגדיר הודעות דואר יפות ומותאמות-אישית) – ייתכן ואין סתירה וניתן לשלב אותן בהצלחה. כלומר, ספרייה אחת לא תוכל להחליף מייד את השנייה, אך הגיוני לקחת חלקים מכל ספרייה ולאחד אותן. חשוב להבין אלו הנחות נעשו על מנת לאפשר את מאפייני האיכות העיקריים של כ לספרייה ולראות שהם לא מתנגשים.
לעומת זאת, אם אחת מתמקדת ב customizability והשנייה ב performance/scalability (שליחת אלפי הודעות בדקה) – קרוב לוודאי שטוב תעשו אם תשמרו אותן כ2 ספריות נפרדות.
לעתים דיי קרובות, ספריות שאיחודן נראה כ no brainer במבט ראשון, מתגלות במהרה כלא סבירות לאיחוד לאחר שבוחנים את מאפייני האיכות שלהן.

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

נ.ב. – חלק מהספרות הפורמלית בנושא מתאר את “מאפייני האיכות” קצת אחרת – אני התייחסתי אליהן בצורה שהייתה שימושית עבורי והוכיחה את עצמה. חפשו בגוגל “Quality Attributes” ותוכלו למצוא חומר רב.



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

ארכיטקטורה: Quality Attributes (חלק א’ – מבוא)

יום אחד בעבודה, ראש הקבוצה שלנו החליט שזמן רב מדי לא הייתה ישיבת קבוצה. הוא ריכז ספונטנית את כל חברי הקבוצה, בעמידה, באחד החדרים ומסר כמה עדכונים בנושאים שונים. אחד מהם היה מינויי לארכיטקט “effective immediately”.לא הייתי מוכן, לא ציפיתי, ולאחר התרגשות קצרה הדבר הראשון שעשיתי הוא “לפנק” את עצמי ב 3 ספרים מאמזון על ארכיטקטורת תוכנה. כלומר – רציתי לדעת “מה עושים ארכיטקטים?”.

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

“באמת ארכיטקטורה זה כ”כ משעמם?” – היה קול אחד שניקר בראשי.
אולי אני לא מבין שומדבר (“you know nothing Jon Snow”) – היה קול ספקני שני.

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

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

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

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

מה מאפייני-איכות הם לא?

הנה screenshot ממצגת שראיתי, אשר ניסתה להסביר “מהם מאפייני איכות”:


המטפורה הזו היא שטותית!

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

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

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

אנסה להסביר מהם מאפייני איכות בעזרת דוגמה: חי-הסוללה של מחשב נייד (Laptop).
על מנת להגיע לחיי סוללה גבוהים של מחשב נייד מנהל המוצר יכול לדרוש “סוללה חזקה במיוחד” שזה סוג של “פ’יצר” – אך זה לא יספיק. על מנת שהמכשיר יחזיק זמן רב ללא טעינה יש להשתמש ברכיבים (מעבד, דיסק) שצורכים מעט חשמל ולעתים קרובות יש תוכנה תומכת (כגון כיבוי משדר ה wi-fi או אפילו מחברי ה USB לאחר זמן ללא שימוש).

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

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

אנו רואים ש”חיי סוללה ארוכים”, עבור מחשב נייד, זו איכות רוחבית של המוצר. “מאפיין איכות” (Quality Attribute), בשפה שלנו.

הנה דוגמה לבחירה בין מאפייני איכות שונים:

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

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

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

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

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

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

מצד שני יבואו המפתחים עם שאלות:
אתה רוצה להשקיע 5 ימי פיתוח בהגנה בפני Directory Traversal[ב]? זו כמובן שאלה לא הוגנת – שרוב אנשי המוצר פשוט לא מסוגלים לענות עליה.
“כמה יהיה ‘ציון האבטחה’ של המערכת שלנו עם ובלי Directory Traversal?” היא שאלת הנגד המעשית של איש המוצר – שהמפתחים מצידם לא יהיו מסוגלים לספק לה תשובה.

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

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

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

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

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

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

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

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

בהצלחה!

—-

[א] ישנם מספר גופים שמגדירים הגדרות לגבי ארכיטקטורה והנדסת תוכנה בכלל. SEI (Software Engineering Institute) היא דוגמה לגוף שכזה. לגופים אלו לרוב יש השפעה רבה על ארגוני-ענק ועל האקדמיה, אך נראה שהשפעתם על כלל התעשייה – מעטה.

[ב] סוג של פגיעות מערכת, בהיבט האבטחה.