CodeGym /בלוג Java /Random-HE /מהן אנטי דפוסים? בואו נסתכל על כמה דוגמאות (חלק 2)
John Squirrels
רָמָה
San Francisco

מהן אנטי דפוסים? בואו נסתכל על כמה דוגמאות (חלק 2)

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

8. פטיש זהב

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

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

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

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

9. אופטימיזציה מוקדמת

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

10. קוד ספגטי

קוד ספגטי הוא אנטי-דפוס המוגדר על ידי קוד שאינו בנוי בצורה גרועה, מבלבל וקשה להבנה, המכיל כל מיני הסתעפות, כגון חריגים, תנאים ולולאות. בעבר, מפעיל ה-goto היה בעל הברית העיקרי של האנטי-דפוס הזה. כבר לא ממש נעשה שימוש בהצהרות Goto, מה שמבטל בשמחה מספר קשיים ובעיות נלווים.
public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
זה נראה נורא, לא? לרוע המזל, זהו האנטי-דפוס הנפוץ ביותר :( אפילו מי שכותב קוד כזה לא יוכל להבין אותו בעתיד. מפתחים אחרים שיראו את הקוד יחשבו, "ובכן, אם זה עובד, אז בסדר - עדיף לא לגעת בזה". לעתים קרובות, שיטה היא בהתחלה פשוטה ושקופה מאוד, אבל ככל שמתווספות דרישות חדשות, השיטה מאוכפת בהדרגה עם עוד ועוד הצהרות מותנות, והופכות אותה למפלצתיות כמו זו. אם שיטה כזו מופיע, אתה צריך לשחזר אותו או לחלוטין או לפחות את החלקים המבלבלים ביותר. בדרך כלל, בעת תזמון פרויקט, זמן מוקצה עבור reactoring, למשל, 30% מזמן הספרינט הוא עבור refactoring ובדיקות. כמובן, זה בהנחה שאין למהר (אבל מתי זה קורה אי פעם) אתה יכול למצוא דוגמה טובה לקוד ספגטי ועיבודו מחדש כאן .

11. מספרי קסם

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

12. תכנות העתק והדבק

תכנות העתק-הדבק הוא אנטי-דפוס שבו הקוד של מישהו אחר מועתק ומודבק ללא מחשבה, דבר שעלול לגרום לתופעות לוואי בלתי צפויות. למשל, שיטות העתקה והדבקה עם חישובים מתמטיים או אלגוריתמים מורכבים שאיננו מבינים עד הסוף. זה עשוי לעבוד במקרה הספציפי שלנו, אבל בנסיבות אחרות זה עלול להוביל לצרות. נניח שאני צריך שיטה כדי לקבוע את המספר המרבי במערך. חיטטתי באינטרנט, מצאתי את הפתרון הזה:
public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
נקבל מערך עם המספרים 3, 6, 1, 4 ו-2, והשיטה מחזירה 6. מעולה, נשמור על זה! אבל מאוחר יותר נקבל מערך המורכב מ-2.5, -7, 2 ו-3, ואז התוצאה שלנו היא -7. והתוצאה הזו לא טובה. הבעיה כאן היא ש- Math.abs() מחזירה את הערך המוחלט. התעלמות מכך מובילה לאסון, אך רק במצבים מסוימים. ללא הבנה מעמיקה של הפתרון, ישנם מקרים רבים שלא תוכל לאמת. קוד מועתק עשוי גם לחרוג מהמבנה הפנימי של האפליקציה, הן מבחינה סגנונית והן ברמה הבסיסית יותר, האדריכלית. קוד כזה יהיה קשה יותר לקריאה ולתחזוקה. וכמובן, אסור לנו לשכוח שהעתקת קוד ישר של מישהו אחר היא סוג מיוחד של פלגיאט. באותם מקרים שבהם מתכנת לא מבין עד הסוף מה הוא או היא עושים ומחליט לקחת את הפתרון העובד כביכול של מישהו אחר, זה לא רק מראה על חוסר התמדה, אלא גם הפעולות הללו פוגעות בצוות, בפרויקט, ולפעמים כל החברה (אז העתק והדבק בזהירות).

13. המצאת הגלגל מחדש

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

14. בעיית יו-יו

בעיית היו-יו היא אנטי-תבנית שבה מבנה האפליקציה מסובך מדי בגלל פיצול יתר (לדוגמה, שרשרת ירושה מחולקת יתר על המידה). "בעיית היו-יו" מתעוררת כאשר אתה צריך להבין תוכנית שההיררכיה של הירושה שלה ארוכה ומורכבת, ויוצרת קריאות מתודות מקוננות עמוקות. כתוצאה מכך, מתכנתים צריכים לנווט בין מחלקות ושיטות רבות ושונות על מנת לבחון את התנהגות התוכנית. השם של האנטי-דפוס הזה מגיע מהשם של הצעצוע. כדוגמה, בואו נסתכל על שרשרת הירושה הבאה: יש לנו ממשק טכנולוגי:
public interface Technology {
   void turnOn();
}
ממשק התחבורה יורש אותו:
public interface Transport extends Technology {
   boolean fillUp();
}
ואז יש לנו ממשק נוסף, GroundTransport:
public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
ומשם, אנו גוזרים כיתת מכוניות מופשטת:
public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
הבא הוא מחלקת פולקסווגן המופשטת:
public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
ולבסוף, דגם ספציפי:
public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
שרשרת זו מאלצת אותנו לחפש תשובות לשאלות כמו:
  1. כמה שיטות VolkswagenAmarokיש?

  2. איזה סוג יש להכניס במקום סימן השאלה על מנת להשיג הפשטה מקסימלית:

    ? someObj = new VolkswagenAmarok();
           someObj.brake();
קשה לענות במהירות על שאלות כאלה - זה מחייב אותנו להסתכל ולחקור, וקל להתבלבל. ומה אם ההיררכיה הרבה יותר גדולה, ארוכה ומסובכת, עם כל מיני עומסים ועקיפות? המבנה שיהיה לנו יהיה מעורפל בגלל פיצול יתר. הפתרון הטוב ביותר יהיה לצמצם את הפילוגים המיותרים. במקרה שלנו, היינו עוזבים את הטכנולוגיה → רכב → פולקסווגן אמארוק.

15. מורכבות מקרית

מורכבות מיותרת היא תבנית אנטי שבה מציגים סיבוכים מיותרים לפתרון.
"כל טיפש יכול לכתוב קוד שמחשב יכול להבין. מתכנתים טובים כותבים קוד שבני אדם יכולים להבין." - מרטין פאולר
אז מהי מורכבות? ניתן להגדיר זאת כדרגת הקושי בה מבוצעת כל פעולה בתוכנית. ככלל, ניתן לחלק את המורכבות לשני סוגים. הסוג הראשון של מורכבות הוא מספר הפונקציות שיש למערכת. ניתן לצמצם אותו רק בדרך אחת - על ידי הסרת פונקציה כלשהי. צריך לעקוב אחר השיטות הקיימות. יש להסיר שיטה אם היא כבר לא בשימוש או שהיא עדיין בשימוש אך מבלי להביא ערך כלשהו. מה שכן, צריך להעריך איך משתמשים בכל השיטות באפליקציה, כדי להבין איפה כדאי השקעות (הרבה שימוש חוזר בקוד) ולמה אפשר להגיד לא. הסוג השני של מורכבות הוא מורכבות מיותרת. ניתן לרפא את זה רק באמצעות גישה מקצועית. במקום לעשות משהו "מגניב" (מפתחים צעירים הם לא היחידים הרגישים למחלה זו), אתה צריך לחשוב איך לעשות את זה פשוט ככל האפשר, כי הפתרון הטוב ביותר הוא תמיד פשוט. לדוגמה, נניח שיש לנו טבלאות קטנות קשורות עם תיאורים של ישויות מסוימות, כגון משתמש: מהן אנטי דפוסים?  בואו נסתכל על כמה דוגמאות (חלק 2) - 3אז יש לנו את המזהה של המשתמש, את מזהה השפה שבה התיאור נוצר ואת התיאור עצמו. באופן דומה, יש לנו מתארי עזר עבור המכוניות, הקבצים, התוכניות וטבלאות הלקוחות. אז איך זה ייראה להכניס ערכים חדשים לטבלאות כאלה?
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
ובהתאם לכך, יש לנו את המספר הזה:
public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
הכל נראה פשוט וטוב... אבל מה עם שאר השיטות? ואכן, לכולם יהיו גם חבורה של switchהצהרות וחבורה של שאילתות מסד נתונים כמעט זהות, מה שבתורן יסבך מאוד וינפח את הכיתה שלנו. איך אפשר להקל על כל זה? בואו נשדרג קצת את המספר שלנו:
@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
כעת לכל סוג יש את שמות השדות המקוריים של הטבלה שלו. כתוצאה מכך, השיטה ליצירת תיאור הופכת:
private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
נוח, פשוט וקומפקטי, אתה לא חושב? האינדיקציה של מפתח טוב היא אפילו לא באיזו תדירות הוא או היא משתמשים בדפוסים, אלא באיזו תדירות הוא או היא נמנעים מתבניות אנטי. בורות היא האויב הגרוע ביותר, כי אתה צריך להכיר את האויבים שלך ממראה עיניים. ובכן, זה כל מה שיש לי להיום. תודה לכולם! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION