CodeGym /בלוג Java /Random-HE /איך עובד ריפאקטורינג ב-Java
John Squirrels
רָמָה
San Francisco

איך עובד ריפאקטורינג ב-Java

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

Refactoring על CodeGym

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

מה זה refactoring?

זה משנה את מבנה הקוד מבלי לשנות את הפונקציונליות שלו. לדוגמה, נניח שיש לנו שיטה שמשווה 2 מספרים ומחזירה אמת אם הראשון גדול ושקרי אחרת :
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if (a == b) {
        return false;
    } else {
        return false;
    }
}
זהו קוד די מסורבל. אפילו מתחילים רק לעתים נדירות כותבים משהו כזה, אבל יש סיכוי. למה להשתמש if-elseבבלוק אם אתה יכול לכתוב את שיטת 6 השורות בצורה תמציתית יותר?
public boolean max(int a, int b) {
     return a > b;
}
כעת יש לנו שיטה פשוטה ואלגנטית המבצעת את אותה פעולה כמו בדוגמה למעלה. כך עובד ה-refactoring: אתה משנה את מבנה הקוד מבלי להשפיע על המהות שלו. ישנן שיטות וטכניקות ריפאקטורינג רבות שנסתכל עליהן מקרוב.

למה אתה צריך refactoring?

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

"הקוד מריח"

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

שיעורים ושיטות גדולות באופן בלתי סביר

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

כיתה גדולה

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

שיטה ארוכה

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

ישנם שני כללים לשחזור שיטה ארוכה:

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

יש כמה דרכים לבטל שיטה ארוכה:

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

שימוש בהרבה סוגי נתונים פרימיטיביים

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

יותר מדי פרמטרים

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

קבוצות של נתונים

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

פתרונות המפרים את עקרונות OOP

ה"ריחות" הללו מתרחשים כאשר מפתח מפר את התכנון התקין של OOP. זה קורה כאשר הוא או היא לא מבינים לחלוטין את יכולות ה-OOP ולא מצליחים להשתמש בהם באופן מלא או נכון.

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

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

החלף הצהרה

מה יכול להיות רע בהצהרה switch? זה רע כשזה הופך להיות מאוד מורכב. בעיה קשורה היא מספר רב של ifהצהרות מקוננות.

שיעורים חלופיים עם ממשקים שונים

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

שדה זמני

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

ריחות שמקשים על שינוי

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

היררכיות ירושה מקבילות

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

תלות בחלוקה אחידה

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

עץ מורכב של שינויים

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

"מריח זבל"

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

מספר רב של הערות בשיטה

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

קוד משוכפל

מחלקות או שיטות שונות משתמשות באותם בלוקים של קוד.

שיעור עצלן

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

קוד לא בשימוש

מחלקה, שיטה או משתנה אינם בשימוש בקוד והם משקל מת.

קישוריות מוגזמת

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

שיטות חיצוניות

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

אינטימיות לא מתאימה

מחלקה תלויה בפרטי היישום של מחלקה אחרת.

שיחות כיתה ארוכות

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

שיעור סוחר משימות

כיתה נחוצה רק לשליחת משימה לכיתה אחרת. אולי צריך להסיר אותו?

טכניקות שחזור

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

חלץ כיתה

מחלקה מבצעת יותר מדי פונקציות. יש להעביר חלק מהם לכיתה אחרת. לדוגמה, נניח שיש לנו Humanמחלקה שמאחסנת גם כתובת בית ויש לה שיטה שמחזירה את הכתובת המלאה:
class Human {
    private String name;
    private String age;
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;

    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }
נוהג טוב להכניס את פרטי הכתובת והשיטה הקשורה (התנהגות עיבוד נתונים) למחלקה נפרדת:
class Human {
   private String name;
   private String age;
   private Address address;

   private String getFullAddress() {
       return address.getFullAddress();
   }
}
class Address {
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}

חלץ שיטה

אם לשיטה יש פונקציונליות כלשהי שניתן לבודד, כדאי למקם אותה בשיטה נפרדת. לדוגמה, שיטה המחשבת את השורשים של משוואה ריבועית:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
    else if (D == 0) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
    else {
        System.out.println("Equation has no roots");
    }
}
אנו מחשבים כל אחת משלוש האפשרויות האפשריות בשיטות נפרדות:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        dGreaterThanZero(a, b, D);
    }
    else if (D == 0) {
        dEqualsZero(a, b);
    }
    else {
        dLessThanZero();
    }
}

public void dGreaterThanZero(double a, double b, double D) {
    double x1, x2;
    x1 = (-b - Math.sqrt(D)) / (2 * a);
    x2 = (-b + Math.sqrt(D)) / (2 * a);
    System.out.println("x1 = " + x1 + ", x2 = " + x2);
}

public void dEqualsZero(double a, double b) {
    double x;
    x = -b / (2 * a);
    System.out.println("x = " + x);
}

public void dLessThanZero() {
    System.out.println("Equation has no roots");
}
הקוד של כל שיטה נעשה הרבה יותר קצר וקל יותר להבנה.

העברת חפץ שלם

כשמתודה נקראת עם פרמטרים, לפעמים אתה עשוי לראות קוד כזה:
public void employeeMethod(Employee employee) {
    // Some actions
    double yearlySalary = employee.getYearlySalary();
    double awards = employee.getAwards();
    double monthlySalary = getMonthlySalary(yearlySalary, awards);
    // Continue processing
}

public double getMonthlySalary(double yearlySalary, double awards) {
     return (yearlySalary + awards)/12;
}
יש employeeMethod2 שורות שלמות המוקדשות לקבלת ערכים ואחסוןם במשתנים פרימיטיביים. לפעמים מבנים כאלה יכולים לקחת עד 10 שורות. הרבה יותר קל להעביר את האובייקט עצמו ולהשתמש בו כדי לחלץ את הנתונים הדרושים:
public void employeeMethod(Employee employee) {
    // Some actions
    double monthlySalary = getMonthlySalary(employee);
    // Continue processing
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}

פשוט, קצר ותמציתי.

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

מדוע ריפקטורינג יעיל

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

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

הספר המפורסם ביותר בנושא ריפאקטורינג הוא "Refactoring. שיפור העיצוב של קוד קיים" מאת מרטין פאולר. יש גם הוצאה מעניינת על ריפקטורינג, המבוססת על ספר קודם: "Refactoring Using Patterns" מאת יהושע קרייבסקי. אם כבר מדברים על דפוסים... בעת עיבוד מחדש, זה תמיד מאוד שימושי לדעת דפוסי עיצוב בסיסיים. הספרים המצוינים הללו יעזרו בכך: אם כבר מדברים על דפוסים... בעת עיבוד מחדש, זה תמיד מאוד שימושי לדעת דפוסי עיצוב בסיסיים. הספרים המצוינים האלה יעזרו בזה:
  1. "דפוסי עיצוב" מאת אריק פרימן, אליזבת רובסון, קתי סיירה וברט בייטס, מהסדרה Head First
  2. "אמנות הקוד הניתן לקריאה" מאת דסטין בוסוול וטרבור פושר
  3. "Code Complete" מאת סטיב מקונל, הקובע את העקרונות לקוד יפה ואלגנטי.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION