JavaRush /Java-Blog /Random-DE /Was sind Antimuster? Schauen wir uns Beispiele an (Teil 1...

Was sind Antimuster? Schauen wir uns Beispiele an (Teil 1)

Veröffentlicht in der Gruppe Random-DE
Was sind Antimuster?  Schauen wir uns Beispiele an (Teil 1) - 1Guten Tag allerseits! Neulich wurde ich interviewt und mir wurde eine Frage zu Antimustern gestellt: Was ist das für ein Biest, was sind ihre Typen und Beispiele in der Praxis? Natürlich habe ich die Frage beantwortet, aber sehr oberflächlich, da ich mich nicht sehr intensiv mit diesem Thema befasst habe. Nach dem Interview begann ich, das Internet zu durchforsten und vertiefte mich immer mehr in dieses Thema. Heute möchte ich einen kurzen Überblick über die beliebtesten Antimuster und ihre Beispiele geben, um Ihnen durch die Lektüre das nötige Wissen zu diesem Thema zu vermitteln. Lass uns anfangen! Bevor wir also diskutieren, was ein Antimuster ist, erinnern wir uns daran, was ein Muster ist. Ein Muster ist ein wiederholbarer Architekturentwurf zur Lösung häufiger Probleme oder Situationen, die beim Entwurf einer Anwendung auftreten. Aber heute sprechen wir nicht über sie, sondern über ihre Gegensätze – Antimuster. Ein Anti-Pattern ist ein gängiger Ansatz zur Lösung einer Klasse häufig auftretender Probleme, der ineffektiv, riskant oder unproduktiv ist. Mit anderen Worten handelt es sich um ein Fehlermuster (manchmal auch Trap genannt). Was sind Antimuster?  Schauen wir uns die Beispiele (Teil 1) - 2 anAntimuster werden in der Regel in folgende Typen unterteilt:
  1. Architektonische Antimuster – architektonische Antimuster, die beim Entwurf der Struktur eines Systems (normalerweise durch einen Architekten) entstehen.
  2. Management-Anti-Pattern – Anti-Patterns im Managementbereich, die in der Regel bei verschiedenen Managern (oder Gruppen von Managern) anzutreffen sind.
  3. Entwicklungs-Anti-Pattern – Anti-Patterns sind Entwicklungsprobleme, die auftreten, wenn normale Programmierer ein System schreiben.
Die Exotik von Antimustern ist viel umfassender, aber wir werden sie heute nicht berücksichtigen, da dies für normale Entwickler überwältigend sein wird. Nehmen wir zunächst ein Beispiel für ein Antimuster im Managementbereich.

1. Analytische Lähmung

Analyseparalyse gilt als klassisches organisatorisches Antimuster. Dabei wird eine Situation bei der Planung überanalysiert, sodass keine Entscheidung getroffen oder Maßnahmen ergriffen werden, was die Entwicklung im Wesentlichen lähmt. Dies geschieht häufig, wenn das Ziel darin besteht, Perfektion zu erreichen und den Analysezeitraum vollständig abzuschließen. Dieses Anti-Pattern zeichnet sich dadurch aus, dass man im Kreis läuft (eine Art geschlossener Kreislauf) und detaillierte Modelle überarbeitet und erstellt, was wiederum den Arbeitsablauf beeinträchtigt. Sie versuchen beispielsweise Dinge vorherzusagen wie: Was passiert, wenn der Benutzer plötzlich eine Liste von Mitarbeitern erstellen möchte, die auf dem vierten und fünften Buchstaben seines Namens basiert, und in die Liste auch die Projekte aufnehmen möchte, denen er zwischen den meisten Arbeitsstunden gewidmet hat? Neujahr und der 8. März in den vier Jahren zuvor? Im Wesentlichen handelt es sich hierbei um eine Überfülle an Analysen. Ein gutes Beispiel aus der Praxis ist, wie die Analyse-Lähmung Kodak in den Bankrott führte . Hier ein paar kleine Tipps gegen Analyse-Lähmungen:
  1. Sie müssen ein langfristiges Ziel als Orientierungspunkt für die Entscheidungsfindung definieren, damit jede Entscheidung, die Sie treffen, Sie Ihrem Ziel näher bringt und Sie nicht dazu zwingt, Zeit zu verlieren.
  2. Konzentrieren Sie sich nicht auf Kleinigkeiten (warum sollten Sie sich für eine kleine Nuance entscheiden, als wäre es die letzte in Ihrem Leben?)
  3. Legen Sie eine Frist für die Entscheidungsfindung fest.
  4. Versuchen Sie nicht, eine Aufgabe perfekt zu erledigen: Es ist besser, sie sehr gut zu erledigen.
Wir werden jetzt nicht zu tief gehen und andere Management-Antimuster in Betracht ziehen. Lassen Sie uns daher ohne Einleitung zu einigen architektonischen Antimustern übergehen, da dieser Artikel höchstwahrscheinlich von zukünftigen Entwicklern und nicht von Managern gelesen wird.

2.Gottobjekt

Das göttliche Objekt ist ein Antimuster, das die übermäßige Konzentration zu vieler unterschiedlicher Funktionen beschreibt und eine große Menge unterschiedlicher Daten speichert (das Objekt, um das sich die Anwendung dreht). Nehmen wir 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 Art große Klasse, die alles auf einmal erledigt. Enthält Abfragen an die Datenbank, enthält einige Daten, wir sehen auch eine Fassadenmethode findAllWithoutPageEnmit Geschäftslogik. Solch ein göttliches Objekt wird riesig und schwerfällig, es angemessen zu tragen. Wir müssen in jedem Teil des Codes daran herumbasteln: Viele Knoten im System sind darauf angewiesen und eng mit ihm verbunden. Die Pflege eines solchen Codes wird immer schwieriger. In solchen Fällen muss es in separate Klassen unterteilt werden, von denen jede nur einen Zweck (Ziel) hat. In diesem Beispiel können Sie es 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 darauf 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 den Dienst zu verschieben:
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, das garantiert, dass es in einer Single-Thread-Anwendung eine einzelne Instanz einer Klasse gibt, und einen globalen Zugriffspunkt auf dieses Objekt bereitstellt. Mehr darüber können Sie hier lesen . Aber ist das ein Muster oder ein Antimuster? Was sind Antimuster?  Schauen wir uns die Beispiele (Teil 1) – 3 anSchauen wir uns die Nachteile dieser Vorlage an:
  1. Globaler Staat. Wenn wir auf eine Instanz einer Klasse zugreifen, kennen wir den aktuellen Status dieser Klasse nicht oder wissen nicht, wer sie wann geändert hat, und dieser Status entspricht möglicherweise nicht unseren Erwartungen. Mit anderen Worten: Die Richtigkeit der Arbeit mit einem Singleton hängt von der Reihenfolge der Aufrufe ab, was dazu führt, dass die Subsysteme voneinander abhängig sind und dadurch die Komplexität der Entwicklung erheblich erhöht.

  2. Singleton verstößt gegen eines der SOLID-Prinzipien – das Single-Responsibility-Prinzip – die Singleton-Klasse kontrolliert nicht nur ihre unmittelbaren Verantwortlichkeiten, sondern auch die Anzahl ihrer Instanzen.

  3. Die Abhängigkeit einer regulären Klasse von einem Singleton ist in der Klassenschnittstelle nicht sichtbar. Da normalerweise eine Instanz eines Singletons nicht in den Parametern einer Methode übergeben wird, sondern direkt über abgerufen wird getInstance(), um die Abhängigkeit einer Klasse von einem Singleton zu identifizieren, müssen Sie sich mit der Implementierung jeder Methode befassen – indem Sie einfach die Öffentlichkeit betrachten Der Vertrag des Objekts reicht nicht aus.

    Das Vorhandensein eines Singletons verringert die Testbarkeit der Anwendung im Allgemeinen und der Klassen, die den Singleton im Besonderen verwenden. Erstens können Sie kein Mock-Objekt anstelle eines Singletons platzieren, und zweitens hängen die Tests voneinander ab, wenn ein Singleton über eine Schnittstelle zum Ändern seines Status verfügt.

    Mit anderen Worten: Ein Singleton erhöht die Konnektivität, und all das ist nichts anderes als eine Folge der erhöhten Konnektivität.

    Und wenn Sie darüber nachdenken, kann die Verwendung eines Singletons vermieden werden. Um beispielsweise die Anzahl der Instanzen eines Objekts zu steuern, ist es durchaus möglich (und notwendig), verschiedene Arten von Fabriken zu verwenden.

    Die größte Gefahr liegt im Versuch, die gesamte Anwendungsarchitektur auf Basis von Singletons aufzubauen. Es gibt viele tolle Alternativen zu diesem Ansatz. Das wichtigste Beispiel ist Spring, nämlich seine IoC-Container: Dort wird das Problem der Kontrolle der Erstellung von Diensten auf natürliche Weise gelöst, da es sich tatsächlich um „Steroidfabriken“ handelt.

    Zu diesem Thema gibt es mittlerweile viel Gerede, sodass es an Ihnen liegt, zu entscheiden, ob es sich bei einem Singleton um ein Muster oder ein Antimuster handelt.

    Und wir wollen uns nicht damit befassen und zum letzten Designmuster für heute übergehen – Poltergeist.

4. Poltergeist

Poltergeist ist ein nicht nützliches Klassen-Antimuster, das zum Aufrufen von Methoden einer anderen Klasse verwendet wird oder einfach eine unnötige Abstraktionsebene hinzufügt. Das Antimuster manifestiert sich in Form kurzlebiger, zustandsloser Objekte. Diese Objekte werden häufig zum Initialisieren anderer, langlebigerer Objekte verwendet.
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 löschen es und verschieben die kleine Funktionalität, die es implementiert, in langlebige Objekte. Als nächstes gehen wir zu den Mustern über, die für uns (als normale Entwickler) am interessantesten sind – Entwicklungs-Antimuster .

5. Harter Code

So kamen wir zu diesem schrecklichen Wort – Hardcode. Der Kern dieses Antimusters besteht darin, dass der Code stark an eine bestimmte Hardwarekonfiguration und/oder Systemumgebung gebunden ist, was es sehr schwierig macht, ihn auf andere Konfigurationen zu portieren. Dieses Antimuster steht in engem Zusammenhang mit magischen Zahlen (sie 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;
}
Genagelt, nicht wahr? Hier legen wir direkt die Konfiguration unserer Verbindung fest; dadurch funktioniert der Code nur mit MySQL ordnungsgemäß, und um die Datenbank zu ändern, müssen Sie in den Code einsteigen und alles manuell ändern. Eine gute Lösung wäre, die Konfigurationen 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 besteht darin, es in Konstanten zu verschieben.

6. Bootsanker

Ein Bootsanker im Kontext von Antipatterns bedeutet das Speichern ungenutzter Teile eines Systems, die nach einer Optimierung oder Umgestaltung übrig bleiben. Außerdem könnten einige Teile des Codes „für die Zukunft“ übrig bleiben, für den Fall, dass Sie sie erneut verwenden müssen. Dadurch wird der Code im Wesentlichen in einen Mülleimer verwandelt. Was sind Antimuster?  Schauen wir uns die Beispiele (Teil 1) – 4 anBeispiel:
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 Update-Methode, die eine separate Methode verwendet, um die Daten des Benutzers aus der Datenbank und der Person, die für das Update gekommen ist, zusammenzuführen (wenn die Person, die für das Update gekommen ist, ein leeres Feld hat, wird es als das alte geschrieben aus der Datenbank). Und es gab beispielsweise die Anforderung, dass Datensätze nicht mit alten zusammengeführt, sondern überschrieben werden sollten, auch wenn leere Felder vorhanden sind:
public User update(Long id, User request) {
   return userDAO.update(user);
}
Infolgedessen mergeUserwird es nicht mehr verwendet und es ist schade, es zu löschen: Was ist, wenn es (oder seine Idee) immer noch nützlich ist? Ein solcher Code verkompliziert und verwirrt die Systeme nur und bietet im Grunde überhaupt keinen praktischen Nutzen. Wir dürfen nicht vergessen, dass es schwierig sein wird, solchen Code mit „toten Teilen“ an einen Kollegen zu übertragen, wenn Sie zu einem anderen Projekt aufbrechen. Die beste Methode, mit Bootsankern umzugehen, ist Code-Refactoring, nämlich das Löschen dieser Codeabschnitte (leider, leider). Außerdem müssen Sie bei der Planung der Entwicklung das Auftreten solcher Anker berücksichtigen (planen Sie Zeit für die Beseitigung der Rückstände ein).

7. Objektgrube

Um dieses Antimuster zu beschreiben, müssen Sie sich zunächst mit dem Objektpoolmuster vertraut machen . Ein Objektpool (Ressourcenpool) ist ein generatives Entwurfsmuster , eine Reihe von Objekten, die initialisiert und einsatzbereit sind. Wenn eine Anwendung ein Objekt benötigt, wird es nicht neu erstellt, sondern aus diesem Pool entnommen. Wenn ein Objekt nicht mehr benötigt wird, wird es nicht zerstört, sondern in den Pool zurückgeführt. Wird normalerweise für schwere Objekte verwendet, deren Erstellung jedes Mal ressourcenintensiv ist, z. B. eine Datenbankverbindung. Schauen wir uns zur Veranschaulichung ein kleines und einfaches Beispiel an. Wir haben also 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);
   }
}
Wir stellen diese Klasse in Form des oben beschriebenen Singleton- Musters/Antimusters dar , d. h. es kann nur ein Objekt dieses Typs geben, sie arbeitet mit bestimmten Objekten Resource, standardmäßig ist der Pool im Konstruktor mit 4 Kopien gefüllt; Wenn ein solches Objekt genommen wird, wird es aus dem Pool entfernt (wenn es nicht vorhanden ist, wird es erstellt und sofort verschenkt), und am Ende gibt es eine Methode, um das Objekt wieder zurückzusetzen. Die Objekte Resourcesehen so aus:
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;
   }
}
Hier haben wir ein kleines Objekt, das eine Karte mit den Namen der Muster als Schlüssel und Links zu ihnen als Wert sowie Methoden für den Zugriff auf die Karte enthält. Lass uns nachsehen main:
class SomeMain {
   public static void main(String[] args) {
       ReusablePool pool = ReusablePool.getInstance();

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

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

       Resource thirdResource = pool.acquireResource();
       Map<String, String> thirdPatterns = firstResource.getPatterns();
       // ......Wieим-то образом используем нашу мапу.....
       pool.releaseResource(thirdResource);
   }
}
Auch hier ist alles klar: Wir nehmen ein Poolobjekt, ziehen ein Objekt mit Ressourcen daraus heraus, nehmen eine Karte daraus, machen etwas damit und legen alles zur weiteren Wiederverwendung zurück in den Pool. Voila: Hier haben Sie das Objektpoolmuster. Aber wir haben doch über Antimuster gesprochen, nicht wahr? Schauen wir uns diesen Fall an main:
Resource fourthResource = pool.acquireResource();
   Map<String, String> fourthPatterns = firstResource.getPatterns();
// ......Wieим-то образом используем нашу мапу.....
fourthPatterns.clear();
firstPatterns.put("first","blablabla");
firstPatterns.put("second","blablabla");
firstPatterns.put("third","blablabla");
firstPatterns.put("fourth","blablabla");
pool.releaseResource(fourthResource);
Auch hier wird ein Ressourcenobjekt genommen, seine Karte mit Mustern wird genommen und etwas damit gemacht, aber vor dem Zurückspeichern im Objektpool wird die Karte bereinigt und mit unverständlichen Daten gefüllt, die dieses Ressourcenobjekt für eine Wiederverwendung ungeeignet machen. Eine der Hauptnuancen eines Objektpools besteht darin, dass ein Objekt nach der Rückgabe in einen für die weitere Wiederverwendung geeigneten Zustand zurückversetzt werden muss. Wenn Objekte nach der Rückgabe an den Pool in einem falschen oder undefinierten Zustand sind, wird dieses Konstrukt als Objekt-Cesspool bezeichnet. Welchen Sinn hat es, Objekte aufzubewahren, die nicht wiederverwendbar sind? In dieser Situation können Sie die interne Karte im Konstruktor unveränderlich machen:
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);
}
(Versuche und der Wunsch, den Inhalt zu ändern, fallen zusammen mit UnsupportedOperationException). Antipatterns sind Fallen, in die Entwickler oft aufgrund von akutem Zeitmangel, Unaufmerksamkeit, Unerfahrenheit oder Tritten von Managern tappen. Der übliche Zeitmangel und die Eile können in der Zukunft zu großen Problemen bei der Bewerbung führen, daher müssen diese Fehler im Voraus bekannt sein und vermieden werden. Was sind Antimuster?  Schauen wir uns Beispiele an (Teil 1) – 6Damit ist der erste Teil des Artikels beendet: Fortsetzung folgt .
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION