CodeGym /Java Blog /무작위의 /안티패턴이란 무엇입니까? 몇 가지 예를 살펴보겠습니다(2부).
John Squirrels
레벨 41
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;
               }
           }
       }
   }
}
끔찍해 보이지 않나요? 불행히도 이것은 가장 일반적인 안티 패턴입니다 :( 그런 코드를 작성하는 사람도 나중에는 이해할 수 없을 것입니다. 코드를 보는 다른 개발자는 "글쎄, 작동한다면 괜찮습니다. 건드리지 않는 것이 좋습니다." 종종 메서드는 처음에는 단순하고 매우 투명하지만 새로운 요구 사항이 추가됨에 따라 메서드는 점점 더 많은 조건문으로 안장되어 이와 같이 괴물로 변합니다. 일반적으로 프로젝트 일정을 잡을 때 리팩토링에 시간이 할당됩니다. 예를 들어 스프린트 시간의 30%는 리팩토링 및 테스트에 사용됩니다. 서두르지 않는다는 것입니다 (하지만 언제 그런 일이 발생합니까).여기 .

11. 매직 넘버

매직 넘버는 모든 종류의 상수가 목적이나 의미에 대한 설명 없이 프로그램에서 사용되는 안티 패턴입니다. 즉, 일반적으로 이름이 잘못 지정되거나 극단적인 경우 댓글이 무엇인지 또는 왜 그런지 설명하는 댓글이 없습니다. 스파게티 코드와 마찬가지로 이것은 가장 일반적인 안티 패턴 중 하나입니다. 코드를 작성하지 않은 사람은 매직 넘버나 작동 방식에 대한 실마리를 가지고 있을 수도 있고 없을 수도 있습니다(시간이 지나면 작성자 자신도 설명할 수 없게 됩니다). 결과적으로 숫자를 변경하거나 제거하면 코드가 마술처럼 함께 작동하지 않게 됩니다. 예를 들어, 3673. 이 안티 패턴에 맞서기 위해 코드 리뷰를 추천합니다. 코드의 관련 섹션에 관여하지 않은 개발자가 코드를 검토해야 합니다. 그들의 눈은 신선할 것이고 그들은 질문을 할 것입니다: 이것은 무엇이며 왜 그렇게 했습니까? 그리고 물론 설명적인 이름을 사용하거나 의견을 남겨야 합니다.

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. 요요 문제

요요 문제는 과도한 조각화(예를 들어 과도하게 세분화된 상속 체인)로 인해 응용 프로그램의 구조가 지나치게 복잡해지는 안티 패턴입니다. "요요 문제"는 상속 계층 구조가 길고 복잡하여 깊게 중첩된 메서드 호출을 생성하는 프로그램을 이해해야 할 때 발생합니다. 결과적으로 프로그래머는 프로그램의 동작을 검사하기 위해 다양한 클래스와 메서드 사이를 탐색해야 합니다. 이 안티패턴의 이름은 장난감의 이름에서 따왔습니다. 예를 들어 다음 상속 체인을 살펴보겠습니다. Technology 인터페이스가 있습니다.

public interface Technology {
   void turnOn();
}
Transport 인터페이스는 다음을 상속합니다.

public interface Transport extends Technology {
   boolean fillUp();
}
그리고 또 다른 인터페이스인 GroundTransport가 있습니다.

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
그리고 거기에서 추상 Car 클래스를 파생시킵니다.

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();
}
다음은 추상 Volkswagen 클래스입니다.

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();
    
이러한 질문에 신속하게 답변하기는 어렵습니다. 살펴보고 조사해야 하며 혼란스러워지기 쉽습니다. 그리고 모든 종류의 오버로드와 오버라이드로 인해 계층 구조가 훨씬 더 크고 길고 복잡하다면 어떻게 될까요? 우리가 가질 수 있는 구조는 과도한 조각화로 인해 가려질 것입니다. 가장 좋은 해결책은 불필요한 분할을 줄이는 것입니다. 우리의 경우에는 Technology → Car → VolkswagenAmarok을 그대로 둡니다.

15. 우발적 복잡성

불필요한 복잡성은 불필요한 복잡성이 솔루션에 도입되는 반 패턴입니다.
"어떤 바보라도 컴퓨터가 이해할 수 있는 코드를 작성할 수 있습니다. 좋은 프로그래머는 인간이 이해할 수 있는 코드를 작성합니다." — 마틴 파울러
그렇다면 복잡성이란 무엇입니까? 프로그램에서 각 작업이 수행되는 난이도로 정의할 수 있습니다. 일반적으로 복잡성은 두 가지 유형으로 나눌 수 있습니다. 복잡성의 첫 번째 종류는 시스템이 가지고 있는 기능의 수입니다. 일부 기능을 제거하여 한 가지 방법으로만 줄일 수 있습니다. 기존 방법을 모니터링해야 합니다. 더 이상 사용되지 않거나 여전히 사용되지만 아무런 가치가 없는 경우 메서드를 제거해야 합니다. 또한 투자가 가치 있는 부분(많은 코드 재사용)과 거절할 수 있는 부분을 이해하기 위해 애플리케이션의 모든 메서드가 어떻게 사용되는지 평가해야 합니다. 복잡성의 두 번째 유형은 불필요한 복잡성입니다. 전문적인 접근을 통해서만 치료할 수 있습니다. "멋진" 일을 하는 대신 (젊은 개발자만이 이 질병에 걸리기 쉬운 것은 아닙니다.) 최상의 솔루션은 항상 간단하기 때문에 가능한 한 간단하게 수행하는 방법에 대해 생각해야 합니다. 예를 들어 사용자와 같은 일부 엔터티에 대한 설명이 있는 작은 관련 테이블이 있다고 가정합니다. 안티패턴이란 무엇입니까?  몇 가지 예를 살펴보겠습니다(2부) - 3따라서 사용자의 ID, 설명이 작성되는 언어의 ID 및 설명 자체가 있습니다. 마찬가지로 자동차, 파일, 계획 및 고객 테이블에 대한 보조 설명자가 있습니다. 그렇다면 그러한 테이블에 새로운 값을 삽입하는 것은 어떤 모습일까요?

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