CodeGym /Java-Blog /Random-DE /Was sind Anti-Patterns? Schauen wir uns einige Beispiele ...
John Squirrels
Level 41
San Francisco

Was sind Anti-Patterns? Schauen wir uns einige Beispiele an (Teil 1)

Veröffentlicht in der Gruppe Random-DE
Guten Tag allerseits! Neulich hatte ich ein Vorstellungsgespräch und mir wurden einige Fragen zu Anti-Patterns gestellt: Was sie sind, welche Arten es gibt und welche praktischen Beispiele es gibt. Natürlich habe ich die Frage beantwortet, aber sehr oberflächlich, da ich mich bisher noch nicht intensiv mit diesem Thema befasst hatte. Nach dem Vorstellungsgespräch begann ich das Internet zu durchforsten und vertiefte mich immer mehr in die Thematik. Was sind Anti-Patterns?  Schauen wir uns einige Beispiele an (Teil 1) - 1 Heute möchte ich einen kurzen Überblick über die beliebtesten Anti-Patterns geben und einige Beispiele besprechen. Ich hoffe, dass Sie durch die Lektüre das nötige Wissen auf diesem Gebiet erlangen. Lass uns anfangen! Bevor wir diskutieren, was ein Anti-Pattern ist, erinnern wir uns daran, was ein Design-Pattern ist. Ein Designmusterist eine wiederholbare Architekturlösung für häufige Probleme oder Situationen, die beim Entwerfen einer Anwendung auftreten. Aber heute reden wir nicht über sie, sondern über ihre Gegensätze – Anti-Muster. Ein Anti-Pattern ist ein weit verbreiteter, aber ineffektiver, riskanter und/oder unproduktiver Ansatz zur Lösung einer Klasse häufiger Probleme. Mit anderen Worten handelt es sich hierbei um ein Fehlermuster (manchmal auch als Falle bezeichnet). Anti-Patterns werden in der Regel in folgende Typen unterteilt:
  1. Architektonische Anti-Muster – Diese Anti-Muster entstehen, wenn die Struktur eines Systems entworfen wird (im Allgemeinen von einem Architekten).
  2. Management-/Organisations-Anti-Patterns – Dies sind Anti-Patterns im Projektmanagement, auf die normalerweise verschiedene Manager (oder Gruppen von Managern) stoßen.
  3. Entwicklungs-Anti-Patterns – Diese Anti-Patterns entstehen, wenn ein System von normalen Programmierern implementiert wird.
Die gesamte Palette der Anti-Patterns ist weitaus exotischer, aber wir werden sie heute nicht alle betrachten. Für normale Entwickler wäre das zu viel. Betrachten wir zunächst ein Management-Anti-Pattern als Beispiel.

1. Analytische Lähmung

Analyselähmunggilt als klassisches Management-Anti-Pattern. Dabei wird die Situation während der Planung übermäßig analysiert, sodass keine Entscheidungen getroffen oder Maßnahmen ergriffen werden, was den Entwicklungsprozess im Wesentlichen lahmlegt. Dies geschieht häufig, wenn das Ziel darin besteht, Perfektion zu erreichen und im Analysezeitraum absolut alles zu berücksichtigen. Dieses Anti-Pattern zeichnet sich dadurch aus, dass man im Kreis läuft (ein gewöhnlicher geschlossener Kreislauf) und detaillierte Modelle überarbeitet und erstellt, was wiederum den Arbeitsablauf beeinträchtigt. Sie versuchen beispielsweise, Dinge auf einer Ebene vorherzusagen: Was aber, wenn ein Benutzer plötzlich eine Liste von Mitarbeitern basierend auf dem vierten und fünften Buchstaben seines Namens erstellen möchte, einschließlich der Liste der Projekte, für die er die meisten Arbeitsstunden aufgewendet hat? zwischen Neujahr und Internationalem Frauentag in den letzten vier Jahren? Im Wesentlichen ist es Das ist zu viel Analyse. Hier ein paar Tipps zur Bekämpfung der Analyselähmung:
  1. Sie müssen ein langfristiges Ziel als Orientierungspunkt für die Entscheidungsfindung definieren, damit Sie mit jeder Ihrer Entscheidungen dem Ziel näher kommen und nicht stagnieren.
  2. Konzentrieren Sie sich nicht auf Kleinigkeiten (warum eine Entscheidung über ein unbedeutendes Detail treffen, als wäre es die wichtigste Entscheidung Ihres Lebens?)
  3. Legen Sie eine Frist für eine Entscheidung fest.
  4. Versuchen Sie nicht, eine Aufgabe perfekt zu erledigen – es ist besser, sie sehr gut zu erledigen.
Es ist nicht nötig, hier zu tief zu gehen, daher werden wir andere Management-Anti-Patterns nicht berücksichtigen. Daher gehen wir ohne Einführung zu einigen architektonischen Anti-Patterns über, da dieser Artikel eher von zukünftigen Entwicklern als von Managern gelesen wird.

2. Gottobjekt

Ein Gottobjekt ist ein Antimuster, das eine übermäßige Konzentration aller möglichen Funktionen und große Mengen unterschiedlicher Daten beschreibt (ein Objekt, um das sich die Anwendung dreht). Nehmen Sie ein kleines Beispiel:

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();
   }
}
Hier sehen wir eine riesige Klasse, die alles kann. Es enthält Datenbankabfragen sowie einige Daten. Wir sehen auch die Fassadenmethode findAllWithoutPageEn, die Geschäftslogik enthält. Ein solches Gottesobjekt wird riesig und es ist schwierig, es richtig zu pflegen. Wir müssen in jedem Teil des Codes damit herumspielen. Viele Systemkomponenten sind darauf angewiesen und eng mit ihm gekoppelt. Es wird immer schwieriger, solchen Code zu pflegen. In solchen Fällen sollte der Code in separate Klassen aufgeteilt werden, die jeweils nur einen Zweck haben. In diesem Beispiel können wir das Gott-Objekt in eine Dao-Klasse aufteilen:

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());
   }
  
                               ........
}
Eine Klasse, die Daten und Methoden für den Zugriff auf die Daten enthält:

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;
   }
                    ....
Und es wäre angemessener, die Methode mit Geschäftslogik in einen Dienst zu verlagern:

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

Ein Singleton ist das einfachste Muster. Es stellt sicher, dass es in einer Single-Thread-Anwendung eine einzige Instanz einer Klasse gibt, und stellt einen globalen Zugriffspunkt auf dieses Objekt bereit. Aber ist es ein Muster oder ein Anti-Muster? Schauen wir uns die Nachteile dieses Musters an:
  1. Globaler Status Wenn wir auf die Instanz der Klasse zugreifen, kennen wir den aktuellen Status dieser Klasse nicht. Wir wissen nicht, wer es wann geändert hat. Der Staat entspricht möglicherweise nicht unseren Erwartungen. Mit anderen Worten: Die Richtigkeit der Arbeit mit einem Singleton hängt von der Reihenfolge der Zugriffe darauf ab. Dies bedeutet, dass Subsysteme voneinander abhängig sind und ein Entwurf dadurch deutlich komplexer wird.

  2. Ein Singleton verstößt gegen die SOLID-Prinzipien – das Single-Responsibility-Prinzip: Zusätzlich zu seinen direkten Aufgaben kontrolliert die Singleton-Klasse auch die Anzahl der Instanzen.

  3. Die Abhängigkeit einer gewöhnlichen Klasse von einem Singleton ist in der Schnittstelle der Klasse nicht sichtbar. Da eine Singleton-Instanz normalerweise nicht als Methodenargument übergeben, sondern direkt über getInstance() abgerufen wird, müssen Sie sich mit der Implementierung jeder Methode befassen, um die Abhängigkeit der Klasse vom Singleton zu identifizieren – indem Sie sich einfach die Öffentlichkeit einer Klasse ansehen Vertrag reicht nicht aus.

    Das Vorhandensein eines Singletons verringert die Testbarkeit der Anwendung als Ganzes und insbesondere der Klassen, die den Singleton verwenden. Erstens können Sie den Singleton nicht durch ein Scheinobjekt ersetzen. Zweitens: Wenn ein Singleton über eine Schnittstelle zum Ändern seines Status verfügt, hängen die Tests voneinander ab.

    Mit anderen Worten: Ein Singleton erhöht die Kopplung, und alles oben Genannte ist nichts anderes als eine Folge einer erhöhten Kopplung.

    Und wenn Sie darüber nachdenken, können Sie die Verwendung eines Singletons vermeiden. Beispielsweise ist es durchaus möglich (und sogar notwendig), verschiedene Arten von Fabriken zu verwenden, um die Anzahl der Instanzen eines Objekts zu steuern.

    Die größte Gefahr liegt im Versuch, eine komplette Anwendungsarchitektur auf Basis von Singletons aufzubauen. Es gibt unzählige wunderbare Alternativen zu diesem Ansatz. Das wichtigste Beispiel ist Spring, nämlich seine IoC-Container: Sie sind eine natürliche Lösung für das Problem der Kontrolle der Erstellung von Diensten, da es sich tatsächlich um „Steroidfabriken“ handelt.

    Zu diesem Thema toben mittlerweile viele endlose und unversöhnliche Debatten. Es liegt an Ihnen zu entscheiden, ob ein Singleton ein Muster oder ein Anti-Muster ist.

    Wir werden uns nicht damit befassen. Stattdessen wenden wir uns dem letzten Designmuster für heute zu – Poltergeist.

4. Poltergeist

Ein Poltergeist ist ein Anti-Pattern mit einer sinnlosen Klasse, das zum Aufrufen von Methoden einer anderen Klasse verwendet wird oder einfach eine unnötige Abstraktionsebene hinzufügt. Dieses Anti-Muster manifestiert sich als kurzlebige Objekte ohne Staat. Diese Objekte werden häufig verwendet, um andere, dauerhaftere Objekte zu initialisieren.

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);
   }
}
Warum brauchen wir ein Objekt, das nur ein Vermittler ist und seine Arbeit an jemand anderen delegiert? Wir eliminieren es und übertragen die geringe Funktionalität, die es hatte, auf langlebige Objekte. Als nächstes wenden wir uns den Mustern zu, die für uns (als normale Entwickler) am interessantesten sind, nämlich den Entwicklungs-Anti-Mustern .

5. Harte Codierung

Damit sind wir bei diesem schrecklichen Wort angelangt: Hard Coding. Der Kern dieses Anti-Patterns besteht darin, dass der Code stark an eine bestimmte Hardwarekonfiguration und/oder Systemumgebung gebunden ist. Dies erschwert die Portierung des Codes auf andere Konfigurationen erheblich. Dieses Anti-Muster ist eng mit magischen Zahlen verbunden (diese Anti-Muster sind oft miteinander verflochten). Beispiel:

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;
}
Tut weh, nicht wahr? Hier codieren wir unsere Verbindungseinstellungen fest. Daher funktioniert der Code nur mit MySQL korrekt. Um die Datenbank zu ändern, müssen wir in den Code eintauchen und alles manuell ändern. Eine gute Lösung wäre, die Konfiguration in einer separaten Datei abzulegen:

spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
Eine andere Möglichkeit ist die Verwendung von Konstanten.

6. Bootsanker

Im Zusammenhang mit Anti-Patterns bedeutet ein Bootsanker , dass Teile des Systems erhalten bleiben, die nach einer Optimierung oder Umgestaltung nicht mehr verwendet werden. Außerdem könnten einige Teile des Codes „zur späteren Verwendung“ aufbewahrt werden, nur für den Fall, dass Sie sie plötzlich benötigen. Im Wesentlichen verwandelt dies Ihren Code in einen Mülleimer. Beispiel:

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());
}
Wir haben eine Aktualisierungsmethode, die eine separate Methode verwendet, um Benutzerdaten aus der Datenbank mit den an die Methode übergebenen Benutzerdaten zusammenzuführen (wenn der an die Aktualisierungsmethode übergebene Benutzer ein Nullfeld hat, wird der alte Feldwert aus der Datenbank übernommen). . Angenommen, es gibt eine neue Anforderung, dass die Datensätze nicht mit den alten zusammengeführt werden dürfen, sondern stattdessen, selbst wenn Nullfelder vorhanden sind, diese zum Überschreiben der alten verwendet werden:

public User update(Long id, User request) {
   return userDAO.update(user);
}
Das bedeutet, dass mergeUser nicht mehr verwendet wird, aber es wäre schade, es zu löschen – was wäre, wenn diese Methode (oder die Idee dieser Methode) eines Tages nützlich sein könnte? Ein solcher Code macht Systeme nur komplizierter und führt zu Verwirrung, da er im Wesentlichen keinen praktischen Wert hat. Wir dürfen nicht vergessen, dass es schwierig sein wird, solchen Code mit „toten Teilen“ an einen Kollegen weiterzugeben, wenn Sie zu einem anderen Projekt aufbrechen. Der beste Weg, mit Bootsankern umzugehen, besteht darin, den Code umzugestalten, dh Codeabschnitte zu löschen (herzzerreißend, ich weiß). Darüber hinaus ist es bei der Erstellung des Entwicklungsplans notwendig, solche Anker zu berücksichtigen (um Zeit für das Aufräumen einzuplanen).

7. Objektgrube

Um dieses Anti-Muster zu beschreiben, müssen Sie sich zunächst mit dem Objektpoolmuster vertraut machen . Ein Objektpool (Ressourcenpool) ist ein Erstellungsentwurfsmuster , ein Satz initialisierter und gebrauchsfertiger Objekte. Wenn eine Anwendung ein Objekt benötigt, wird es aus diesem Pool entnommen und nicht neu erstellt. Wenn ein Objekt nicht mehr benötigt wird, wird es nicht zerstört. Stattdessen wird es in den Pool zurückgeführt. Dieses Muster wird normalerweise für schwere Objekte verwendet, deren Erstellung jedes Mal, wenn sie benötigt werden, zeitaufwändig ist, beispielsweise beim Herstellen einer Verbindung zu einer Datenbank. Schauen wir uns ein kleines und einfaches Beispiel an. Hier ist eine Klasse, die dieses Muster darstellt:

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);
   }
}
Diese Klasse wird in Form des obigen Singleton- Musters/Anti-Musters dargestellt, dh es kann nur ein Objekt dieses Typs geben. Es verwendet bestimmte ResourceObjekte. Standardmäßig füllt der Konstruktor den Pool mit 4 Instanzen. Wenn Sie ein Objekt erhalten, wird es aus dem Pool entfernt (wenn kein Objekt verfügbar ist, wird eines erstellt und sofort zurückgegeben). Und am Ende haben wir eine Methode, um das Objekt zurückzusetzen. Ressourcenobjekte sehen folgendermaßen aus:

public class Resource {
   private Map<String, String> patterns;
   public Resource() {
       patterns = new HashMap<>();
       patterns.put("proxy", "https://en.wikipedia.org/wiki/Proxy_pattern");
       patterns.put("bridge", "https://en.wikipedia.org/wiki/Bridge_pattern");
       patterns.put("facade", "https://en.wikipedia.org/wiki/Facade_pattern");
       patterns.put("builder", "https://en.wikipedia.org/wiki/Builder_pattern");
   }
   public Map<String, String> getPatterns() {
       return patterns;
   }
   public void setPatterns(Map<String, String> patterns) {
       this.patterns = patterns;
   }
}
Hier haben wir ein kleines Objekt, das eine Karte mit Entwurfsmusternamen als Schlüssel und entsprechenden Wikipedia-Links als Wert sowie Methoden für den Zugriff auf die Karte enthält. Werfen wir einen Blick auf main:

class SomeMain {
   public static void main(String[] args) {
       ReusablePool pool = ReusablePool.getInstance();

       Resource firstResource = pool.acquireResource();
       Map<String, String> firstPatterns = firstResource.getPatterns();
       // use our map somehow...
       pool.releaseResource(firstResource);

       Resource secondResource = pool.acquireResource();
       Map<String, String> secondPatterns = firstResource.getPatterns();
       // use our map somehow...
       pool.releaseResource(secondResource);

       Resource thirdResource = pool.acquireResource();
       Map<String, String> thirdPatterns = firstResource.getPatterns();
       // use our map somehow...
       pool.releaseResource(thirdResource);
   }
}
Hier ist alles klar genug: Wir holen uns ein Poolobjekt, holen uns ein Objekt mit Ressourcen aus dem Pool, holen uns die Karte vom Ressourcenobjekt, machen etwas damit und legen das alles an seinem Platz im Pool zur weiteren Wiederverwendung ab. Voila, das ist das Designmuster für den Objektpool. Aber wir haben über Anti-Patterns gesprochen, oder? Betrachten wir den folgenden Fall in der Hauptmethode:

Resource fourthResource = pool.acquireResource();
   Map<String, String> fourthPatterns = firstResource.getPatterns();
// use our map somehow...
fourthPatterns.clear();
firstPatterns.put("first","blablabla");
firstPatterns.put("second","blablabla");
firstPatterns.put("third","blablabla");
firstPatterns.put("fourth","blablabla");
pool.releaseResource(fourthResource);
Auch hier erhalten wir ein Ressourcenobjekt, dessen Musterkarte und machen etwas mit der Karte. Doch bevor die Karte wieder im Objektpool gespeichert wird, wird sie gelöscht und dann mit beschädigten Daten gefüllt, wodurch das Ressourcenobjekt für die Wiederverwendung ungeeignet wird. Eines der Hauptdetails eines Objektpools besteht darin, dass ein Objekt bei der Rückgabe in einen für die weitere Wiederverwendung geeigneten Zustand wiederhergestellt werden muss. Wenn in den Pool zurückgegebene Objekte in einem falschen oder undefinierten Zustand bleiben, wird unser Design als Objekt-Cesspool bezeichnet. Ist es sinnvoll, Gegenstände aufzubewahren, die nicht zur Wiederverwendung geeignet sind? In dieser Situation können wir die interne Karte im Konstruktor unveränderlich machen:

public Resource() {
   patterns = new HashMap<>();
   patterns.put("proxy", "https://en.wikipedia.org/wiki/Proxy_pattern");
   patterns.put("bridge", "https://en.wikipedia.org/wiki/Bridge_pattern");
   patterns.put("facade", "https://en.wikipedia.org/wiki/Facade_pattern");
   patterns.put("builder", "https://en.wikipedia.org/wiki/Builder_pattern");
   patterns = Collections.unmodifiableMap(patterns);
}
Versuche und der Wunsch, den Inhalt der Karte zu ändern, werden dank der UnsupportedOperationException, die sie generieren, verschwinden. Anti-Patterns sind Fallen, auf die Entwickler aufgrund akuten Zeitmangels, Nachlässigkeit, Unerfahrenheit oder Druck seitens der Projektmanager häufig stoßen. Das häufige Überstürzen kann in der Zukunft zu großen Problemen für die Anwendung führen. Daher müssen Sie über diese Fehler Bescheid wissen und sie im Voraus vermeiden. Damit ist der erste Teil des Artikels abgeschlossen. Fortgesetzt werden...
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION