ריפקטורינג של אפליקציית מובייל: היכן מצטבר החוב
החוב הטכני של אפליקציית מובייל רק לעיתים רחוקות נראה כמו מודול שבור אחד. בדרך כלל הוא מצטבר בשקט: שכבת הרשת התכסתה בחריגים עבור מסכים ספציפיים, כללי העסק התפשטו בין המסך, המטמון והאנליטיקה, ובסיס הקוד שהתנפח הפסיק להיכנס לראש אפילו של מהנדס בכיר. שינוי מבצע אדם אחד, וההשלכות מפרק כל הצוות.
הצימוד בין המודולים גדל יחד עם מספר התרחישים. כדי להציג כרטיס חדש בקטלוג, יש להכניס שינוי במודל הנתונים, בשכבת הטעינה, בפריסת הרשימה ובאחסון המקומי — ארבעה מקומות, שמתוכם לשלושה אין קשר למשימה. ככל שההדבקה צפופה יותר, כך קשה יותר לחזות מה בדיוק יישבר במסך הסמוך.
לאחר מכן נכנסת רגרסיה אחרי השינוי. שינוי פורמט התגובה גורר בדיקה של השלמת הזמנה, התחברות והתראות, כי כולם קוראים את אותם נתונים. ריצה ידנית נמשכת ימים, בדיקות אוטומטיות לא מכסות את כל הענפים, והוצאת הגרסה נדחית למועד לא ידוע.
עבור המוצר זה מתבטא בכך ששיפורים קטנים מפסיקים להיות קטנים. הצוות מתכנן לא את מה שדרוש לעסק, אלא את מה שבטוח לשנות; במקום פיתוח מופיעות קבוצות תיקונים ושחרורים לא מתוכננים. המפתחים מתחילים לפחד לגעת באזורים ישנים — והחוב גדל עוד יותר מהר.
עם זאת, המצטבר מדיד: לפי מספר הקשרים בין מודולים, זמן הבנייה, שיעור המסלולים ללא בדיקות ומספר התקלות לכל שחרור. המספרים האלה מראים היכן בדיוק לחתוך. בהמשך נסביר כיצד זה נסגר.
האפקט של ריפקטורינג: מהירות שחרורי גרסה ופחות כשלים
ריפקטורינג מוצדק כאשר השלכותיו נראות במדדי מחזור השחרור, ולא באסתטיקה של הקוד. כל עוד מסכים, שכבת רשת ואחסון מחוברים ישירות, שינוי במקום אחד גורר שרשרת שינויים: הבנייה נמשכת יותר זמן, הבדיקה ידנית, וחלק מהזמן נאכל על ידי רגרסיות שצצות כבר אחרי הפרסום.
הפרדה למודולים בלתי תלויים משנה את התמונה. אזורים נבנים ונבדקים במקביל, שינוי טיפוסי מתרכז בתוך מודול אחד, ובדיקות אוטומטיות לתרחישים קריטיים מורצות על כל שינוי בקוד.
האצת הוצאת הגרסאות כאן אינה סיסמה: שיעור הזמן שהצוות משקיע בהכנת השחרור מצטמצם פי כמה, וההוצאה עצמה הופכת לפעולה מתוכננת ולא למרתון חירום.
את הירידה במספר הכשלים אנו מודדים לפי עובדות: שיעור ההפעלות ללא תקלה ב-Crashlytics, מספר ההחזרות לאחר פרסום, ותדירות החזרה לגרסה קודמת. גבולות ברורים בין מודולים מסירים סוג שלם של קריסות — אלו שנולדו במפגש בין שכבות והופיעו רק תחת עומס.
בנפרד אנו מריצים תרחישי שיא: העמידות לעומס עולה, כי פעולות כבדות יוצאות מהתהליך הראשי, וקריאות רשת חוזרות אינן משוכפלות.
האפקט הרביעי — עצימות העבודה בתחזוקת האפליקציה וצפיות האומדן. כאשר כל מודול אחראי לתחומו, משימה חדשה נאמדת לפי היקף העבודה בתוך אזור אחד, ולא לפי "מה שיצא": פיזור האומדנים מצטמצם משבועות לימים, ומחזור השחרור מתוכנן לפי לוח זמנים ולא בדיעבד.
שיטות עבודה מחדש: מלאה, הדרגתית, ממוקדת
את אופן העבודה מחדש בוחרים לפי שתי שאלות: עד כמה הבעיה עמוקה בארכיטקטורה והאם ניתן להקפיא שחרורים בזמן העבודה. אף אחת מהאפשרויות אינה דורשת עצירת מוצר פעיל — ההבדל הוא בכמה קוד מעבר הצוות סוחב במקביל וכמה זמן שני שכבות חיות בו-זמנית.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| אופטימיזציה ממוקדת של צווארי בקבוק | אחד-שניים תרחישים נפגעים: התחלה קרה, גלילה ברשימה ארוכה, סנכרון ברקע. שאר הליבה מספקת | מטפל בסימפטום. החוב בשכבות הגישה לנתונים והניווט נשאר, ואחרי כמה איטרציות השינויים חוזרים לאותם מודולים |
| עבודה מחדש הדרגתית | המוצר מתפתח באופן פעיל, שחרורים יוצאים כל שבועיים, עצירה בלתי אפשרית | השכבות הישנה והחדשה חיות במקביל: נפח קוד המעבר גדל, בכל שלב נדרשות בדיקות תאימות |
| הוצאת מודולים לספרייה נפרדת | לוגיקה משותפת חוזרת בשתי אפליקציות או יותר או בצוותים | דורש חוזה גרסאות ומחזור שחרורים משלו, אחרת הבניות מתפצלות והמשתמשים מקבלים התנהגות שונה |
| כתיבה מחדש מלאה של הליבה | הארכיטקטורה לא מאפשרת לפתח את המוצר, ממשקי הפלטפורמה התיישנו, ותיקונים עולים יותר ממימוש חדש | תמיכה מקבילה בענף הישן עד שהחדש ישיג שוויון ביכולות, ומחזור בדיקות מלא מחדש |
מתחילים בדרך כלל באופטימיזציה ממוקדת: היא מראה במהירות היכן צוואר הבקבוק האמיתי. אם השינויים אחריה חוזרים לאותם מודולים, עוברים לעבודה מחדש הדרגתית ולהוצאת חלקים משותפים לספרייה נפרדת, ואת הכתיבה מחדש המלאה של הליבה משאירים למקרה שבו הארכיטקטורה הפסיקה לשאת את עומס הפיתוח.
איך העבודה מתבצעת: שלבים מהאודיט ועד למסירה
ריפקטורינג שובר את המוצר כשהוא מתבצע בכתיבה מחדש אחת גדולה: בזמן שהענף חי שבועות, השחרורים נעצרים, והרגרסיה צצה כבר אצל המשתמשים. אנו הולכים בצעדים קצרים, אחרי כל אחד האפליקציה נשארת ניתנת לבנייה ועובדת. הסדר שלהלן הוא טיפוסי — עבור פרויקט ספציפי משתנים ההיקף והעומק.
- אודיט של בסיס הקוד. מנתחים את היסטוריית השינויים ומפת התלויות, מוצאים כפילויות ומודולים שמשתנים בתדירות הגבוהה ביותר. התוצר — רשימת צווארי בקבוק עם הערכת סיכון וסדר עדיפויות לעבודה.
- סכימת המבנה היעד. מפרקים את האפליקציה לשכבות ומודולים, מקבעים חוזים ביניהם. מה מעבירים כמו שהוא ומה כותבים מחדש — מתאמים עם הצוות שלכם לפני השורה הראשונה של הקוד החדש.
- מודול פיילוט. מעבירים אזור אחד בשלמותו, כדי לבדוק את הסכימה על קוד חי ולהעריך את עצימות העבודה של השאר. בהמשך מרחיבים גישה מאומתת, ולא הנחות.
- העברה בחלקים. הולכים במנות קטנות: כל שינוי מכוסה בבדיקה ונכנס לענף הראשי בלי להמתין לסיום העבודה. השחרורים ממשיכים לצאת לפי לוח הזמנים הרגיל.
- בנייה אוטומטית ובדיקות. מגדירים בנייה על כל שינוי, ניתוח סטטי והרצת בדיקות. הרגרסיה נתפסת לפני סקירת הקוד, ולא ביום ההפצה.
- בדיקות קבלה. משווים את ההתנהגות לגרסה המקורית לפי תרחישי מפתח — כניסה, תשלום, מצב לא מקוון. זמן ההתחלה ושיעור הקריסות לא אמורים להחמיר באף שלב.
- מסירה והדרכת הצוות. מוסרים תיעוד על המבנה, כללים לביצוע שינויים וסקירה עם מהנדסי הלקוח. האפליקציה נשארת ניתנת לתחזוקה בלעדינו.
ארטיפקטים של הפרויקט: סכימות, נהלים, תיעוד
ריפקטורינג הושלם לא כאשר הבנייה הפכה לירוקה, אלא כאשר במאגר נשארו ארטיפקטים שלפיהם הצוות עובד בלעדינו. כל אחד מהם נמצא לצד קוד המקור ומתעדכן באותו שינוי, ולכן אינו מתפצל מהקוד אחרי חודש.
- מפת מודולים ותלויות — אילו מודולים קיימים, מי תלוי במי, היכן נותקו קשרים מעגליים ואיזו שכבה אחראית למה.
- תיעוד ארכיטקטורה — סכימת שכבות וגבולות תחומים, נקודות הרחבה, יומן החלטות עם נימוקים וחלופות שנדחו.
- נוהל עבודה עם קוד — מודל הסתעפות, סדר סקירת שינויים הדדית, הגבלה על גודלם, בדיקות חובה לפני מיזוג.
- תיאור הבדיקות — אילו בדיקות אוטומטיות מכסות אילו מסלולים, היכן עובר גבול הכיסוי ומה נשאר לבדיקה ידנית.
- הוראות להוצאת גרסה — סדר הבנייה, הכנת החתימה, הפצה לערוץ וחזרה לגרסה הקודמת.
- מפת בעלויות — איזה מודול משויך לאחראי ולמי לפנות עם שאלה.
- יומן חוב טכני — מה נדחה בכוונה, לפי אילו סימנים נחזור ומה יקרה אם לא נחזור.
בודקים את הערכה בפשטות: מהנדס שלא השתתף בריפקטורינג בונה גרסה לפי ההוראות. אם הצליח בלי רמזים — הארטיפקטים מלאים; אם נתקע — משלימים לפני המסירה.
מקרה בוחן: קיצרנו את זמן הוצאת הגרסה פי שלושה
מה היה
האפליקציה התפתחה כמה שנים ללא ארכיטקטורה משותפת: לוגיקת המסכים, העבודה עם הרשת ואחסון המצב היו מעורבבים, ואותו חישוב שוכפל בארבעה מקומות. בסיס הקוד ב-Flutter נבנה בפקודה אחת, אבל כל שינוי גרר אחריו שרשרת של תיקונים קשורים.
הוצאת גרסה נמשכה שישה שבועות, מהם שלושה הוקדשו לייצוב לאחר רגרסיות. כ-40% מהמשימות הוחזרו לעבודה נוספת, 60% מהשינויים דרשו תיקונים במודולים סמוכים, ומהנדס חדש הגיע למשימות עצמאיות רק אחרי חודש.
מה שינינו ומה התוצאה
חילקנו את האפליקציה לשכבות עם גבולות ברורים: תצוגה, לוגיקה עסקית, גישה לנתונים. חישובים משותפים רוכזו במודול אחד, שכבת הרשת נסגרה בלקוח אחיד עם טיפול בשגיאות, ומסלולים קריטיים — תשלום, התחברות, סנכרון — כוסו בבדיקות אוטומטיות. את ההוצאה לאור העברנו לקו ייצור, שבו בנייה, בדיקות וחתימה מתבצעות בפקודה אחת ללא צעדים ידניים.
זמן הוצאת הגרסה התקצר פי שלושה — משישה שבועות לשניים. שיעור המשימות שהוחזרו לעבודה נוספת ירד מ-40% ל-8%, שיעור השינויים עם תיקונים במודולים סמוכים — מ-60% ל-15%.
הכנסת מהנדס חדש לפרויקט נמשכת כחמישה ימי עבודה במקום שלושה-ארבעה שבועות: המבנה צפוי, והבדיקות מראות שגיאה לפני סקירה ידנית של הקוד.
במה שונה כתיבה מחדש מאפס מעבודה מחדש הדרגתית?
כתיבה מחדש מאפס היא פרויקט שני לצד הראשון: הלוגיקה מועברת לארכיטקטורה נקייה, והקוד הישן מוקפא. הדחה הדרגתית של מודולים מתבצעת אחרת — שכבה חדשה מופיעה בתוך אפליקציה עובדת ולוקחת אזור אחר אזור.
היקר בהחלפה מלאה אינו היקף העבודה, אלא ההפסקה באספקה. בזמן ההעברה, לא יכולות חדשות ולא תיקונים יוצאים, הדרישות מתקדמות, וההשקה נדחית בשיעור הפער הזה. הסיכון בהחלפה מלאה הוא שהכשל כאן הוא אחד וכללי: הפלטפורמה לא יוצאת חלקית — היא לא יוצאת כלל.
החלפה מלאה מוצדקת כאשר הבעיה ביסוד: הקשרים מפוזרים על פני מסכים, אין היכן לחבר מודול חדש, בדיקות לא נכתבות, ושינוי גורר אחריו חצי מהאפליקציה. אז ההדחה עולה יותר — כל שכבה מתכסה הסתייגויות, והתשתית אוכלת את הרווח.
עבודה מחדש הדרגתית כמעט תמיד מביאה תוצאה מהר יותר. המודול המוחלף הראשון מגיע למשתמשים אחרי מחזור-שניים ונבדק על התנהגות חיה; בהמשך שיעור הקוד החדש עולה, והחזרה של שלב מוגבלת לאזור אחד. שחרורי המוצר לא נעצרים, ולוחות הזמנים של העבודה מחדש מחושבים לפי שלבים, ולא לפי תאריך אחד.
את הבחירה עושים לפי מדידה: כמה קוד באמת צריך להחליף וכמה תלויות חיצוניות מחזיקות באזור הזה. אם משתנה פחות משליש והקשרים מתירים — הולכים דרך הדחה הדרגתית של מודולים. אם הליבה לא מאפשרת להיאחז — מתכננים החלפה מלאה ומחליטים מראש איך חי הענף הנוכחי עד המעבר.
ספקות טכניים נפוצים לפני תחילת העבודה
ריפקטורינג של אפליקציית מובייל כמעט תמיד מתבצע על מוצר חי: אי אפשר לעצור לחלוטין את הוצאת הגרסאות, אחרת המשתמשים לא מקבלים תיקונים, והמיקומים בחנויות נשחקים. לכן העבודה מחדש מחולקת לשינויים קצרים — כל אחד עובר בנייה ונכנס לשחרור הקרוב בנפרד, והענף הראשי בכל רגע נשאר מוכן לפרסום.
חלק מהקוד אכן נשאר כתוב מחדש באופן חלקי, וזהו מצב עבודה, לא חוסר גמירות: גבולות המודולים מקובעים, השכבה הישנה נקראת דרך ממשקים יציבים ומוסרת לפי מוכנות ההחלפה.
האם יהיה צורך להשהות את הוצאת הגרסאות בזמן העבודה?
לא. העבודה מתבצעת במקביל למחזור השחרורים: שינויים נכנסים אחד אחד, כל אחד עובר בדיקות לפני מיזוג. אם שינוי לא עובר, הוא נשאר בענף נפרד ואינו מעכב את שאר המשימות.
כיצד אתם מוודאים שדבר לא נשבר?
בדיקת הרגרסיה מבוססת על בדיקות אוטומטיות במסלולים קריטיים — הפעלה, התחברות, תשלום, מעברים בין מסכים — ומורצת על כל שינוי ב-Appium. בנוסף, אנו משווים דוחות תקלות של הבנייה החדשה לשחרור הקודם: עלייה בולטת בשגיאות חוסמת מיזוג עד לבירור הסיבות.
עד כמה מדויק אתם מעריכים את לוחות הזמנים של העבודה?
האומדן נעשה לפי מודולים, ולא לפי הקוד כולו: תחילה מיפוי תלויות וכיסוי בדיקות, אחר כך סדר העבודה ונקודות עצירה בלי לאבד את התפקוד. כך לוחות הזמנים מחושבים מחדש עם סגירת כל מודול, ואתם רואים את ההיקף בכל שלב, ולא מספר כללי אחד.
נדון במשימה ונחזור עם תוכנית עבודה
הערכת ריפקטורינג בלי גישה לקוד הופכת לטווח "מכאן ועד כאן", שכבר בשבוע הראשון מתפצל מהמציאות. כדי לתת לוחות זמנים לפי שלבים וסדר עבודה, אנו זקוקים לנתוני פתיחה: איך בנוי המאגר, איך יוצאים השחרורים ואיפה בדיוק כואב עכשיו. שלחו בצורה נוחה — טקסט, קישור או הודעה קולית, ננתח.
- גישה למאגר: די בהרשאת קריאה — חשובים מבנה המודולים, היסטוריית השינויים והענף שממנו נבנה השחרור.
- סכימת הוצאת גרסאות: באיזו תדירות יוצאות בניות, מי בונה אותן, אילו ערוצי בדיקה מעורבים והיכן נשאר צעד ידני.
- תיאור צווארי בקבוק: בנייה ארוכה, קריסות בזמן עבודת האפליקציה, כפילות לוגיקה בין מסכים, מקומות שמתקנים בזהירות.
- מפת מודולים ומסכים עם ציון אילו נמצאים בעבודה פעילה ואילו מוקפאים.
- מה אסור לשבור: חוזים עם הצד השרת, סכימת אחסון נתונים במכשיר, מערכת העיצוב, התנהגות ההתחברות.
- מצב הבדיקות האוטומטיות: שיעור המסלולים המכוסים, מה נופל במערך הבדיקות ומה נבדק רק ידנית.
- סדרי עדיפויות של המוצר למחזור הקרוב — משימות שבגללן אסור לעצור את הריפקטורינג לזמן רב.
עם הסט הזה אנו חוזרים עם תוכנית: רצף השלבים, מה נכלל בכל אחד, מה אנו משאירים בכוונה מחוץ לתחום, לוחות זמנים ונקודות תיאום היקף. תארו את המשימה — נחזור עם אומדן ותוכנית, בהמשך נקבע את סדר העבודה ונתחיל.







