JavaRush /Java Blogu /Random-AZ /Antipatternlər nədir? Nümunələrə baxaq (1-ci hissə)

Antipatternlər nədir? Nümunələrə baxaq (1-ci hissə)

Qrupda dərc edilmişdir
Antipatternlər nədir?  Nümunələrə baxaq (1-ci hissə) - 1Hamıya xeyirli gün! Keçən gün müsahibə verdim və mənə antipatternlər haqqında sual verdilər: bu necə bir heyvandır, onların növləri və praktikada nümunələri nədir. Əlbəttə ki, suala cavab verdim, amma çox səthi, çünki bu məsələnin tədqiqinə çox dərindən getməmişəm. Müsahibədən sonra interneti gəzməyə başladım, getdikcə bu mövzuya dalırdım. Bu gün sizə bu mövzuda lazımi biliklər verə biləcək ən məşhur antipatternlər və onların nümunələrini qısaca nəzərdən keçirmək istərdim. Gəlin başlayaq! Beləliklə, antipatternin nə olduğunu müzakirə etməzdən əvvəl, nümunənin nə olduğunu xatırlayaq. Nümunə, tətbiqi tərtib edərkən ortaya çıxan ümumi problemlərin və ya vəziyyətlərin həlli üçün təkrarlana bilən bir memarlıq dizaynıdır. Ancaq bu gün biz onlar haqqında deyil, onların əksləri - antipatternlər haqqında danışırıq. Anti-naxış səmərəsiz , riskli və ya məhsuldar olmayan tez-tez rast gəlinən problemlərin həlli üçün ümumi yanaşmadır. Başqa sözlə, bu, səhv nümunəsidir (bəzən tələ də deyilir). Antipatternlər nədir?  Nümunələrə baxaq (1-ci hissə) - 2Bir qayda olaraq, antipatternlər aşağıdakı növlərə bölünür:
  1. Memarlıq antipatternləri - sistemin strukturunun layihələndirilməsi zamanı yaranan memarlıq antipatternləri (adətən memar tərəfindən).
  2. İdarəetmə Anti Pattern - adətən müxtəlif menecerlər (və ya menecerlər qrupları) tərəfindən rast gəlinən idarəetmə sahəsində antipatternlər.
  3. İnkişaf Anti Pattern - antipatternlər adi proqramçılar bir sistem yazarkən yaranan inkişaf problemləridir.
Antipatternlərin ekzotikliyi daha genişdir, lakin bu gün onları nəzərdən keçirməyəcəyik, çünki adi tərtibatçılar üçün bu, həddən artıq ağır olacaq. Başlamaq üçün idarəetmə sahəsində bir antipattern nümunəsini götürək.

1. Analitik iflic

Analiz iflici klassik təşkilati anti-naxış hesab olunur. Bu, planlaşdırma zamanı vəziyyəti həddindən artıq təhlil etməyi əhatə edir ki, heç bir qərar və ya tədbir görülməsin, mahiyyətcə inkişafı iflic etsin. Bu, çox vaxt məqsəd mükəmməlliyə nail olmaq və təhlil dövrünün tam başa çatması olduqda baş verir. Bu anti-naxış dairələrdə gəzmək (bir növ qapalı döngə), təfərrüatlı modelləri nəzərdən keçirmək və yaratmaqla xarakterizə olunur ki, bu da öz növbəsində iş axınına mane olur. Məsələn, siz belə şeyləri proqnozlaşdırmağa çalışırsınız: əgər istifadəçi birdən öz adının dördüncü və beşinci hərfləri əsasında işçilərin siyahısını yaratmaq istəsə, o cümlədən Yeni il ilə ən çox iş vaxtını ayırdığı layihələr əvvəlki dörd ildə səkkizinci mart? Əslində, bu, təhlilin həddindən artıq çoxluğudur. Yaxşı real həyat nümunəsi analiz iflicinin Kodak-ı necə iflasa aparmasıdır . Analiz iflici ilə mübarizə aparmaq üçün bir neçə sürətli ipucu:
  1. Uzunmüddətli hədəfi qərar qəbul etmək üçün mayak kimi müəyyənləşdirməlisiniz ki, verdiyiniz hər bir qərar sizi hədəfinizə yaxınlaşdırsın və sizi vaxtı qeyd etməyə məcbur etməsin.
  2. Diqqətinizi xırda şeylərə cəmləməyin (niyə həyatınızdakı sonuncu nüans kimi kiçik bir nüansa qərar verin?)
  3. Qərar qəbul etmək üçün son tarix təyin edin.
  4. Bir işi mükəmməl yerinə yetirməyə çalışmayın: bunu çox yaxşı etmək daha yaxşıdır.
Biz çox dərinə getməyəcəyik və indi digər idarəetmə antipatternlərini nəzərdən keçirəcəyik. Buna görə də, preambula olmadan, bəzi memarlıq antipatternlərinə keçək, çünki çox güman ki, bu məqalə menecerlər deyil, gələcək tərtibatçılar tərəfindən oxunur.

2.Allah etiraz edir

İlahi obyekt çoxlu sayda müxtəlif verilənləri (tətbiqin ətrafında fırlanan obyekt) saxlayaraq çoxlu müxtəlif funksiyaların həddindən artıq konsentrasiyasını təsvir edən anti-naxışdır. Kiçik bir misal götürək:
public class SomeUserGodObject {
   private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
   private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";
   private static final String FIND_ALL_CUSTOMERS = "SELECT id, u.email, u.phone, u.first_name_en, u.middle_name_en, u.last_name_en, u.created_date" +
           "  WHERE u.id IN (SELECT up.user_id FROM user_permissions up WHERE up.permission_id = ?)";
   private static final String FIND_BY_EMAIL = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_dateFROM users WHERE email = ?";
   private static final String LIMIT_OFFSET = " LIMIT ? OFFSET ?";
   private static final String ORDER = " ORDER BY ISNULL(last_name_en), last_name_en, ISNULL(first_name_en), first_name_en, ISNULL(last_name_ru), " +
           "last_name_ru, ISNULL(first_name_ru), first_name_ru";
   private static final String CREATE_USER_EN = "INSERT INTO users(id, phone, email, first_name_en, middle_name_en, last_name_en, created_date) " +
           "VALUES (?, ?, ?, ?, ?, ?, ?)";
   private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
                                  ........
   private final JdbcTemplate jdbcTemplate;
   private Map<String, String> firstName;
   private Map<String, String> middleName;
   private Map<String, String> lastName;
   private List<Long> permission;
                                   ........
   @Override
   public List<User> findAllEnCustomers(Long permissionId) {
       return jdbcTemplate.query( FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
   }
   @Override
   public List<User> findAllEn() {
       return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
   }
   @Override
   public Optional<List<User>> findAllEnByEmail(String email) {
       var query = FIND_ALL_USERS_EN + FIND_BY_EMAIL + ORDER;
       return Optional.ofNullable(jdbcTemplate.query(query, userRowMapper(), email));
   }
                              .............
   private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
       switch (type) {
           case USERS:
               return findAllEnUsers(permissionId);
           case CUSTOMERS:
               return findAllEnCustomers(permissionId);
           default:
               return findAllEn();
       }
   }
                              ..............private RowMapper<User> userRowMapperEn() {
       return (rs, rowNum) ->
               User.builder()
                       .id(rs.getLong("id"))
                       .email(rs.getString("email"))
                       .accessFailed(rs.getInt("access_counter"))
                       .createdDate(rs.getObject("created_date", LocalDateTime.class))
                       .firstName(rs.getString("first_name_en"))
                       .middleName(rs.getString("middle_name_en"))
                       .lastName(rs.getString("last_name_en"))
                       .phone(rs.getString("phone"))
                       .build();
   }
}
Burada hər şeyi bir anda yerinə yetirən bir növ böyük sinif görürük. Verilənlər bazasına sorğuları ehtiva edir, bəzi məlumatları ehtiva edir, biz həmçinin findAllWithoutPageEnbiznes məntiqi ilə fasad metodunu görürük. Belə bir ilahi obyekt nəhəng olur və adekvat şəkildə dəstəklənməsi çətin olur. Biz hər bir kod parçasında bununla məşğul olmalıyıq: sistemdəki bir çox qovşaq ona güvənir və ona möhkəm bağlıdır. Belə kodu saxlamaq getdikcə çətinləşir. Belə hallarda onu ayrı-ayrı siniflərə bölmək lazımdır ki, onların hər biri yalnız bir məqsəd (məqsəd) olacaq. Bu nümunədə onu dao sinfinə ayıra bilərik:
public class UserDaoImpl {
   private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
   private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";

                                   ........
   private final JdbcTemplate jdbcTemplate;

                                   ........
   @Override
   public List<User> findAllEnCustomers(Long permissionId) {
       return jdbcTemplate.query(FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
   }
   @Override
   public List<User> findAllEn() {
       return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
   }

                               ........
}
Məlumat və ona daxil olmaq üçün üsullardan ibarət sinif:
public class UserInfo {
   private Map<String, String> firstName;..
   public Map<String, String> getFirstName() {
       return firstName;
   }
   public void setFirstName(Map<String, String> firstName) {
       this.firstName = firstName;
   }
                    ....
Və metodu biznes məntiqi ilə xidmətə köçürmək daha məqsədəuyğun olardı:
private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
   switch (type) {
       case USERS:
           return findAllEnUsers(permissionId);
       case CUSTOMERS:
           return findAllEnCustomers(permissionId);
       default:
           return findAllEn();
   }
}

3.Singleton

Singleton, tək yivli proqramda hansısa sinifin tək nümunəsinin olacağına zəmanət verən və həmin obyektə qlobal giriş nöqtəsini təmin edən ən sadə nümunədir. Bu barədə daha ətraflı burada oxuya bilərsiniz . Ancaq bu bir nümunədir, yoxsa antipattern? Antipatternlər nədir?  Nümunələrə baxaq (1-ci hissə) - 3Bu şablonun çatışmazlıqlarına baxaq:
  1. Qlobal dövlət. Bir sinfin nümunəsinə daxil olduqda, biz həmin sinfin cari vəziyyətini və ya onu kimin və ya nə vaxt dəyişdirdiyini bilmirik və bu vəziyyət gözlədiyimiz kimi olmaya bilər. Başqa sözlə, singletonla işləməyin düzgünlüyü ona edilən çağırışların ardıcıllığından asılıdır ki, bu da alt sistemlərin bir-birindən asılı olmasına səbəb olur və nəticədə inkişafın mürəkkəbliyini ciddi şəkildə artırır.

  2. Singleton SOLID prinsiplərindən birini - Vahid Məsuliyyət Prinsipini pozur - Singleton sinfi öz bilavasitə öhdəliklərini yerinə yetirməklə yanaşı, nümunələrinin sayına da nəzarət edir.

  3. Normal sinfin singletondan asılılığı sinif interfeysində görünmür. Bir qayda olaraq, bir singleton nümunəsi metodun parametrlərində ötürülmür, lakin birbaşa vasitəsilə əldə edilir, getInstance()bir sinfin singletondan asılılığını müəyyən etmək üçün, hər bir metodun həyata keçirilməsini araşdırmaq lazımdır - sadəcə olaraq ictimaiyyətə baxmaq obyektin müqaviləsi kifayət deyil.

    Singiltonun mövcudluğu ümumiyyətlə tətbiqin və xüsusilə singletondan istifadə edən siniflərin sınaqdan keçirilməsini azaldır. Birincisi, Singleton yerinə Sınaq obyekti qoya bilməzsiniz, ikincisi, əgər singletonun vəziyyətini dəyişdirmək üçün interfeysi varsa, testlər bir-birindən asılı olacaq.

    Başqa sözlə, singleton əlaqəni artırır və yuxarıda göstərilənlərin hamısı artan əlaqənin nəticəsidir.

    Və bu barədə düşünsəniz, singletonun istifadəsindən qaçınmaq olar. Məsələn, bir obyektin nümunələrinin sayını idarə etmək üçün müxtəlif növ fabriklərdən istifadə etmək olduqca mümkündür (və zəruridir).

    Ən böyük təhlükə, bütün tətbiq arxitekturasını singletonlar əsasında qurmaq cəhdindədir. Bu yanaşmanın bir çox əla alternativləri var. Ən vacib nümunə Bahardır, yəni onun IoC konteynerləri: orada xidmətlərin yaradılmasına nəzarət problemi təbii şəkildə həll olunur, çünki onlar əslində "steroidlər üzrə zavodlardır".

    İndi bu mövzuda çoxlu holivar var, buna görə də təktonun naxış və ya antipattern olub-olmamasına qərar vermək sizin ixtiyarınızdadır.

    Və biz bunun üzərində dayanmayacağıq və bu gün üçün son dizayn nümunəsinə keçməyəcəyik - poltergeist.

4. Poltergeist

Poltergeist başqa sinfin metodlarını çağırmaq üçün istifadə olunan və ya sadəcə olaraq lazımsız abstraksiya qatını əlavə edən qeyri-faydalı sinif antipatterndir. Antipattern dövlətdən məhrum olan qısamüddətli obyektlər şəklində özünü göstərir. Bu obyektlər tez-tez digər, daha davamlı obyektləri işə salmaq üçün istifadə olunur.
public class UserManager {
   private UserService service;
   public UserManager(UserService userService) {
       service = userService;
   }
   User createUser(User user) {
       return service.create(user);
   }
   Long findAllUsers(){
       return service.findAll().size();
   }
   String findEmailById(Long id) {
       return service.findById(id).getEmail();}
   User findUserByEmail(String email) {
       return service.findByEmail(email);
   }
   User deleteUserById(Long id) {
       return service.delete(id);
   }
}
Sadəcə olaraq vasitəçi olan və öz işini başqasına həvalə edən obyekt niyə bizə lazımdır? Biz onu silirik və tətbiq etdiyi kiçik funksionallığı uzunömürlü obyektlərə köçürürük. Sonra, biz (adi inkişaf etdiricilər kimi) bizi ən çox maraqlandıran nümunələrə keçirik - inkişaf antipatterns .

5. Sərt kod

Beləliklə, biz bu dəhşətli sözə gəldik - hardcode. Bu antipatternin mahiyyəti kodun xüsusi aparat konfiqurasiyasına və/və ya sistem mühitinə güclü şəkildə bağlı olmasıdır ki, bu da onu digər konfiqurasiyalara köçürməyi çox çətinləşdirir. Bu antipattern sehrli nömrələrlə sıx bağlıdır (onlar tez-tez bir-birinə bağlıdır). Misal:
public Connection buildConnection() throws Exception {
   Class.forName("com.mysql.cj.jdbc.Driver");
   connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8&characterSetResults=UTF-8&serverTimezone=UTC", "user01", "12345qwert");
   return connection;
}
Nailed, elə deyilmi? Burada birbaşa əlaqəmizin konfiqurasiyasını təyin etdik, nəticədə kod yalnız MySQL ilə düzgün işləyəcək və verilənlər bazasını dəyişdirmək üçün siz koda daxil olub hər şeyi əl ilə dəyişdirməlisiniz. Konfiqurasiyaları ayrı bir fayla qoymaq yaxşı bir həll olardı:
spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
Başqa bir seçim onu ​​sabitlərə köçürməkdir.

6. Qayıq lövbəri

Antipatternlər kontekstində bir qayıq lövbəri, müəyyən optimallaşdırma və ya refaktorinqdən sonra qalan sistemin istifadə olunmamış hissələrini saxlamaq deməkdir. Həmçinin, kodun bəzi hissələri yenidən istifadə etməli olduğunuz halda “gələcək üçün” qala bilər. Bu, əslində kodu zibil qutusuna çevirir. Antipatternlər nədir?  Nümunələrə baxaq (1-ci hissə) - 4Misal:
public User update(Long id, User request) {
   User user = mergeUser(findById(id), request);
   return userDAO.update(user);
}
private User mergeUser(User findUser, User requestUser) {
   return new User(
           findUser.getId(),
           requestUser.getEmail() != null ? requestUser.getEmail() : findUser.getEmail(),
           requestUser.getFirstName() != null ? requestUser.getFirstName() : findUser.getFirstNameRu(),
           requestUser.getMiddleName() != null ? requestUser.getMiddleName() : findUser.getMiddleNameRu(),
           requestUser.getLastName() != null ? requestUser.getLastName() : findUser.getLastNameEn(),
           requestUser.getPhone() != null ? requestUser.getPhone() : findUser.getPhone());
}
İstifadəçinin məlumat bazasından və yeniləməyə gələnin məlumatlarını birləşdirmək üçün ayrıca üsuldan istifadə edən yeniləmə metodumuz var (əgər yeniləmə üçün gələn şəxsin boş sahəsi varsa, o zaman köhnə kimi yazılır. verilənlər bazasından). Məsələn, qeydlərin köhnələrlə birləşdirilməməsi, boş sahələr olsa belə, üzərinə yazılması tələbi var idi:
public User update(Long id, User request) {
   return userDAO.update(user);
}
Nəticədə, mergeUserartıq istifadə edilmir və onu silmək təəssüf doğurur: əgər o (və ya onun ideyası) hələ də faydalıdırsa? Bu cür kod yalnız sistemləri çətinləşdirir və çaşdırır, mahiyyətcə heç bir praktiki dəyər vermir. Unutmamalıyıq ki, "ölü parçalar" olan belə kodu başqa bir layihəyə getdiyiniz zaman bir həmkarınıza köçürmək çətin olacaq. Qayıq lövbərləri ilə məşğul olmağın ən yaxşı üsulu kodun refaktorinqidir, yəni kodun bu bölmələrini silməkdir (təəssüf ki,). Həmçinin, inkişafı planlaşdırarkən, bu cür lövbərlərin meydana gəlməsini nəzərə almalısınız (tullantıları təmizləmək üçün vaxt verin).

7.Obyekt tullantıları

Bu antipatterni təsvir etmək üçün əvvəlcə obyekt hovuz nümunəsi ilə tanış olmalısınız . Obyekt hovuzu (resurs hovuzu) generativ dizayn nümunəsidir , işə salınmış və istifadəyə hazır olan obyektlər dəstidir. Tətbiq obyekt tələb etdikdə, o, yenidən yaradılmır, lakin bu hovuzdan götürülür. Bir obyektə ehtiyac qalmadıqda, o, məhv edilmir, lakin hovuza qaytarılır. Adətən verilənlər bazası bağlantısı kimi hər dəfə yaratmaq üçün resurs tələb edən ağır obyektlər üçün istifadə olunur. Nümunə üçün kiçik və sadə bir nümunəyə baxaq. Beləliklə, bu nümunəni təmsil edən bir sinifimiz var:
class ReusablePool {
   private static ReusablePool pool;
   private List<Resource> list = new LinkedList<>();
   private ReusablePool() {
       for (int i = 0; i < 3; i++)
           list.add(new Resource());
   }
   public static ReusablePool getInstance() {
       if (pool == null) {
           pool = new ReusablePool();
       }
       return pool;
   }
   public Resource acquireResource() {
       if (list.size() == 0) {
           return new Resource();
       } else {
           Resource r = list.get(0);
           list.remove(r);
           return r;
       }
   }
   public void releaseResource(Resource r) {
       list.add(r);
   }
}
Biz bu klassı yuxarıda təsvir edilən singleton model/antippattern şəklində təqdim edirik , yəni bu tipdə yalnız bir obyekt ola bilər, müəyyən obyektlər üzərində işləyir Resource, konstruktorda standart olaraq hovuz 4 nüsxə ilə doldurulur; belə bir obyekt götürüldükdə hovuzdan çıxarılır (əgər orada deyilsə, yaradılır və dərhal verilir), sonda isə obyekti geri qoymaq üsulu var. Obyektlər Resourcebelə görünür:
public class Resource {
   private Map<String, String> patterns;
   public Resource() {
       patterns = new HashMap<>();
       patterns.put("заместитель", "https://studfile.net/preview/3676297/page:3/");
       patterns.put("мост", "https://studfile.net/preview/3676297/page:4/");
       patterns.put("фасад", "https://studfile.net/preview/3676297/page:5/");
       patterns.put("строитель", "https://studfile.net/preview/3676297/page:6/#16");
   }
   public Map<String, String> getPatterns() {
       return patterns;
   }
   public void setPatterns(Map<String, String> patterns) {
       this.patterns = patterns;
   }
}
Burada açar kimi nümunələrin adları və onlara dəyər kimi keçidlər, həmçinin xəritəyə daxil olmaq üsulları olan xəritəni ehtiva edən kiçik bir obyektimiz var. Baxaq main:
class SomeMain {
   public static void main(String[] args) {
       ReusablePool pool = ReusablePool.getInstance();

       Resource firstResource = pool.acquireResource();
       Map<String, String> firstPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(firstResource);

       Resource secondResource = pool.acquireResource();
       Map<String, String> secondPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(secondResource);

       Resource thirdResource = pool.acquireResource();
       Map<String, String> thirdPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(thirdResource);
   }
}
Burada da hər şey aydındır: biz hovuz obyektini götürürük, ondan resursları olan bir obyekti çıxarırıq, ondan xəritə götürürük, onunla nəsə edirik və yenidən istifadə üçün hamısını yenidən hovuza qoyuruq. Voila: burada obyekt hovuz nümunəsi var. Amma biz antipatternlərdən danışırdıq, elə deyilmi? Gəlin bu işə baxaq main:
Resource fourthResource = pool.acquireResource();
   Map<String, String> fourthPatterns = firstResource.getPatterns();
// ......Howим-то образом используем нашу мапу.....
fourthPatterns.clear();
firstPatterns.put("first","blablabla");
firstPatterns.put("second","blablabla");
firstPatterns.put("third","blablabla");
firstPatterns.put("fourth","blablabla");
pool.releaseResource(fourthResource);
Burada yenə də resurs obyekti götürülür, onun naxışlı xəritəsi götürülür və onunla nəsə edilir, lakin yenidən obyekt hovuzuna saxlamazdan əvvəl xəritə təmizlənir və anlaşılmaz məlumatlarla doldurulur ki, bu da bu Resurs obyektini təkrar istifadə üçün yararsız edir. Obyekt hovuzunun əsas nüanslarından biri odur ki, obyekt qaytarıldıqdan sonra onun sonrakı təkrar istifadə üçün uyğun vəziyyətə qaytarılması lazımdır. Əgər obyektlər hovuza qaytarıldıqdan sonra səhv və ya qeyri-müəyyən vəziyyətdədirsə, bu konstruksiya obyektin çuxuru adlanır. Yenidən istifadə olunmayan obyektləri saxlamağın mənası nədir? Bu vəziyyətdə, konstruktorda daxili xəritəni dəyişməz edə bilərsiniz:
public Resource() {
   patterns = new HashMap<>();
   patterns.put("заместитель", "https://studfile.net/preview/3676297/page:3/");
   patterns.put("мост", "https://studfile.net/preview/3676297/page:4/");
   patterns.put("фасад", "https://studfile.net/preview/3676297/page:5/");
   patterns.put("строитель", "https://studfile.net/preview/3676297/page:6/#16");
   patterns = Collections.unmodifiableMap(patterns);
}
(məzmunu dəyişdirmək cəhdləri və istəyi UnsupportedOperationException ilə birlikdə düşəcək). Antipatternlər , tərtibatçıların kəskin vaxt çatışmazlığı, diqqətsizlik, təcrübəsizlik və ya menecerlərin zərbələri səbəbindən tez-tez düşdüyü tələlərdir. Adi vaxtın olmaması və tələsik gələcəkdə tətbiq üçün böyük problemlərə səbəb ola bilər, buna görə də bu səhvləri əvvəlcədən bilmək və qarşısını almaq lazımdır. Antipatternlər nədir?  Nümunələrə baxaq (1-ci hissə) - 6Bununla da məqalənin birinci hissəsi başa çatdı: davam etdiriləcək .
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION