👺

Anti-Pattern

D A S H B O A R D
D E V E L O P
S E C U R I T Y
 안티 패턴이란?
 The Blob
 Lava Flow
 Poltergeists
Reference

 안티 패턴이란?

안티패턴은 비효율적이거나 생산성이 저해되는, 다시 말해서 '권장사항'의 반대편에 있는 소프트웨어 설계 관행을 의미 - Google Cloud
디자인 패턴을 공부하려던 중 ‘왜 디자인패턴을 알아야 할까?’ 라는 당연하지만 아주 1차원적인 의문을 가지게 되었다.
정답은 안티패턴에 있었고, 안티 패턴은 특정 상황에서 반복적으로 발생하는 문제에 대한 잘못된 해결책(패턴) 혹은 비효율적인 해결책(패턴)을 의미한다고 한다.
즉, 좋지 않은 패턴을 적용할 경우 안티 패턴으로서 작용해 당장 문제를 해결하는 것 같지만 장기적으로 보면 더 큰 문제를 야기하거나 기존의 문제를 더 악화시키는 경향이 있기 때문에 디자인 패턴을 알아야 한다.
그렇게 이 글에서는 디자인 패턴을 공부하기 전 안티 패턴을 먼저 알아보고자 한다.
본 글은 많은 안티 패턴 중 아래 페이지에 있는 안티 패턴을 정리하고자 한다.

 The Blob

이름: The Blob ( Winnebago and The God Class)
자주 발생하는 곳: Application
해결책: Refactoring of Responsibilities
발생 원인: ’아.. 귀찮아’와 ‘빨리 빨리’
안티 패턴인 이유: 기능, 성능 복잡성 관리
안티패턴의 증거: "This is the class that is really the heart of our architecture." → 이 클래스는 우리 아키텍처의 핵심이다.

The Blob 이름의 유래

영화 The Blob에서 유래된 이름으로 해당 영화에서 우주에서 온 물방울만한 젤리가 지구에 도착하는데, 이 물방울만한 젤리가 사람들을 먹으면 먹을 수록 커지는데, 많은 사람들은 이를 무시했고, 한 과학자는 사람을 잡아먹으며 커지는 생명체가 알고 있음에도 무시한 결과 Blob은 사람들을 엄청나게 많이 먹고 결국은 지구가 멸망시킬 수 있는 위협이 된다는 스토리이다.
즉, 객체 지향 아키텍처 전체를 잠식하는 안티 패턴을 The Blob이라 부르게 된 것이다.

일반적인 Blob의 형태

일반적인 Blob은 왼쪽과 같이 하나의 단일 클래스가 모든 처리를 독점하고 나머지 클래스들은 그저 데이터를 캡슐화만 하는 디자인에서 주로 발생한다.
이렇기 때문에 Blob은 부분의 프로세스가 포함되고 다른 객체에는 데이터가 포함됩니다. 즉, 블롭을 사용하는 아키텍처는 프로세스와 데이터가 분리되어 있으므로 객체 지향 아키텍처라기보다는 절차적 스타일이다.
다른 말로는 Blob이 의도된 결과로서 볼 수 있지만 대부분은 부적절한 요구 사항의 할당의 결과일 경우가 많다.
예를 들면, PoC가 끝난 코드가 시간이 지나면서 개발 단계 → 프로토 타입 단계 → 프로덕션 단계로 진하하는 중에 발생하는 반복적인 결과물인 경우가 이에 해당한다.
SOLID 원칙으로 보면 SRP(단일 책임 원칙)이 지켜지지 않은 것으로 위에서 정리한 ‘이 클래스는 우리 아키텍처의 핵심이다.’ 가 딱 들어 맞는 다고 생각한다.

Blob의 판단과 영향

무엇을 Blob이라 부를 수 있을까?
많은 수의 연산과 속성 혹은 모두를 가지고 있는 단일 클래스로 속성과 연산이 60개 이상인 클래스
서로 관련이 없는 속성과 연산이 단일 클래스에 캡슐화된 이질적인 컬렉션
객체 지향 설계의 부재 → 캡슐화가 잘 되지 않았음을 의미
Blob이 어떠한 영향을 끼칠까?
객체 지향 설계의 고유한 장점을 손상, 즉, 단일 책임 원칙을 따르지 않아 광범위하게 영향을 끼친다.
일반적으로 재사용 및 테스트하기에는 너무 복잡하다.
간단한 작업에도 과도한 리소스를 사용하여 메모리에 로드하는 데 비용이 발생한다.

Blob의 원인

객체 지향 아키텍처의 부족
→ 객체 지향을 이해하지 못했다거나, 추상화 기술이 부족한 것일 수 있다.
아키텍처의 부족(어떤 아키텍처든)
→ 시스템 구성 요소, 상호 작용 및 선택한 프로그래밍 언어의 특정 사용에 대한 정의가 없는 경우 → 의도한 목적과 다른 용도로 사용되기 때문에 Blob이 발생할 수 있다.
부적절한 아키텍처의 사용
개발자들의 제한적 개입
→ 개발자들이 사소한 기능들을 이미 존재하고 있는 클래스에 작성하기 보다는 새로운 클래스를 생성한 후 작은 단일 클래스를 너무 많이 만들게 된다.
요구사항의 문제
→ 종종 요구사항이 위에서 말한 것과 같이 절차적 스타일의 프로세스일 경우 Blob이 발생할 확률이 높다.

리펙터링

1.
관련 속성 및 작업을 식별하거나 분류
전체 시스템 내에서 공통의 초점, 동작 또는 기능과 직접적으로 관련되어 있다는 점에서 응집력이 있어야한다.
예를 들어 옆에 있는 그림을 보면, Item관련 작업들과 Catalog 관련 작업들로 분류할 수 있다.
2.
분류한 속성 및 작업을 마이그레이션
위에서 분류한 속성과 작업을 "자연스러운 홈"을 찾은 다음 해당 컬렉션으로 마이그레이션하는 것이 두번째 단계이다.
옆에 그림을 통해보자면 연두색 화살표와 핑크색 화살표와 같이 관련 속성을 가진 속성과 작업별로 마이그레이션을 진행해준다.
3.
모든 중복된 간접 연결을 제거
중복된 간접 연결이란 A→B와 결합되어 있고, B→C랑 결합되어 있다면, 멀리 결합(’far-coupled’)되었다는 표현을 사용하는데 이를 중복된 간접 연결이라 할 수 있다.
예제에서 ITEM 클래스는 각 항목이 실제로는 CATALOG에 속하고, 이는 다시 MAIN에 속한다는 점에서 처음에는 MAIN 클래스와 멀리 결합되어 있다.
4.
공통 기본 클래스로 마이그레이션
만약 간접 연결된 클래스들을 적절히 제거할 수 있다고 한다면, 파생된 클래스를 공통의 기본 클래스로 마이그레이션 한다.
옆 그림에서는 ITEM 클래스가 파생 클래스로서 제거 대상이 되며, ITEM을 CATALOG 클래스로 마이그레이션을 진행한다.
5.
일시적인 연관관계들의 제거
모든 일시적인 연결을 제거하여 적절하게 속성 및 연산 인수에 대한 유형 지정자로 대체한다.
옆 그림에서는 Check로 시작하는 것들이 일시적인 연결을 하는 매서드로 별도의 인스턴스 클래스로 이동시키면 좋다.
Blob이 무작정 나쁘다는 것은 아니다?
Blob이 무작정 나쁘다고 할 수는 없다. 왜냐하면 보통 래거시 시스템들을 유지보수 하기 위해서 소프트웨어의 파티셔닝을 필요로 하지 않고, Blob 방식으로 최종 코드 레이어만 추가하는 방식을 사용하는 것이 좋기 때문이다.

 Lava Flow

이름: Lava Flow (Dead Code)
자주 발생하는 곳: Application
해결책: Architectural Configuration Management
발생 원인: ’아.. 귀찮아’와 ‘뭐 정리 좀 안하면 어때’
안티 패턴인 이유: 기능, 성능 복잡성 관리
안티패턴의 증거: "Oh that! Well Ray and Emil (they're no longer with the company) wrote that routine back when Jim (who left last month) was trying a workaround for Irene's input processing code (she's in another department now, too). I don't think it's used anywhere now, but I'm not really sure. Irene didn't really document it very clearly, so we figured we would just leave well enough alone for now. After all, the bloomin' thing works doesn't it?!"

Lava Flow 이름의 유래

기존에 개발된 굉장히 복잡한 시스템을 조사하면서 방대한 양의 코드에 포함된 특정 구성 요소에 대해 많은 개발자와 인터뷰를 진행했다고 한다.
이때 계속해서 같은 대답을 들었다고 한다. "저 클래스는 제가 여기 오기 전에 작성된 것인데 무슨 용도로 쓰이는지 모르겠어요." 그리고 점차 이 복잡한 시스템을 구성하는 실제 코드의 30~50%는 현재 작업 중인 사람이 이해하지 못하거나 문서화되어 있지 않다는 사실을 깨달았다고 한다.
해당 코드를 분석한 결과 현재 사용하지 않는 코드이며 오래전 개발자들이 시도했던 접근 방식에서 비롯된 사실을 알게 되었는데, 그럼에도 불구하고 직원들은 분명히 뛰어나지만 자신이 작성하지 않았거나 용도를 모르는 코드를 수정하거나 삭제하는 것을 꺼려했고, 그 이유나 수정 방법을 모른 채 무언가를 망칠까 봐 두려워 했다고 한다.
이 시점에서 이 코드 덩어리를 '용암'이라고 부르기 시작했는데, 현무암처럼 단단하고 한 번 굳으면 제거하기 어렵다는 점에서 유래한 유동적인 성질을 표현한 것이라고 한다.

일반적인 Lava Flow의 형태

// TODO // 절대 지우지 말기! (실제로는 필요없는 코드) // ~~~~ TEST // 요구사항 ~~~~ 수정 class IndexFrame extends Frame { // IndexFrame constructor // --------------------------- public IndexFrame(String index_parameter_1) { // Note: need to add additional stuff here... super (str); } // ---------------------------
Java
복사
Lava Flow 안티패턴은 연구용으로 시작했지만 결국 프로덕션으로 전환된 시스템에서 흔히 발견된다. 이 패턴은 이전 개발 버전의 용암과 같은 '흐름'이 코드 환경에 흩어져 있는 것이 특징이며 용얌이 굳어 현무암으로 변해 일반적으로 쓸모 없이 굳어져 아무도 기억 못하는 특징과 비슷하다.
이는, 개발자들이 처음에 MVP를 만들기 위해 문서작업을 모두 포기하고 여러가지 방법을 시도하며 만들었기 때문에 보통 발생한다.
그 결과 전체 시스템과 명확하게 관련이 없는 여러 코드 조각, 변덕스러운 변수 클래스, 프로시저가 생겨나고, 너무 복잡하고 방대해 보이기 때문에 중요해 보이지만 실제로는 어떤 기능을 하는지, 왜 존재하는지 아무도 설명할 수 없는 경우가 많다.
그런데 여기서 많은 개발자들이 실제로 악영향을 끼치는 코드도 아니고, 실제로 중요할 수도 있다고 생각해 그냥 내버려두기로 하는 것이 잘못된 생각이다.
그렇기 때문에 처음부터, 코드 스멜을 제거하고 모든 작업들은 문서로 남겨 두는 것이 가장 바람직하다고 할 수 있다.

Lava Flow의 판단과 영향

무엇을 Lava Flow라 부를 수 있을까?
시스템에서 정당화할 수 없는 변수와 코드 조각이 자주 등장
문서화되지 않은 복잡하고 중요해 보이는 함수, 클래스 또는 시스템 아키텍처와 명확하게 관련이 없는 세그먼트
매우 느슨한 아키텍처
설명이나 문서가 없는 주석 처리된 전체 코드 블록
'유동적'이거나 '교체 예정'인 코드 영역
사용하지 않는 코드
사용하지 않거나, 설명할 수 없거나, 더 이상 사용되지 않는 인터페이스
Lava Flow가 어떠한 영향을 끼칠까?
다른 영역에서 코드가 재사용되면서 계속 확산될 수 있다.
후속 개발자가 너무 급하거나 겁이 나서 원래의 플로우를 분석하지 못하고 원래의 플로우를 우회하는 새로운 2차 플로우를 계속 생성하면서 기하급수적으로 증가
코드를 문서화하거나 개선할 수 있을 만큼 아키텍처를 빠르게 이해하는 것이 불가능해짐
분석, 검증, 테스트하는 데 많은 비용 발생
메모리에 로드하는 데 많은 비용이 들기 때문에 중요한 리소스를 낭비
객체 지향 디자인의 고유한 장점을 많이 잃게 됩

Lava Flow의 원인

구성 관리에 대한 고려 없이 프로덕션에 배치된 연구 단계의 코드
미완성 코드의 통제되지 않은 배포. 일부 기능 구현을 위한 여러 가지 시험적 접근 방식의 구현
단일 개발자가 작성한 코드
구성 관리가 부족하거나 프로세스 관리 정책을 준수하지 않은 코드
아키텍처 부족 또는 아키텍처 중심이 아닌 개발
반복적인 개발 프로세스 → 소프트웨어 프로젝트의 목표가 불분명하거나 반복적으로 변경되는 경우가 많기 때문에 아키텍처에 대한 고려와 문서화가 무기한 연기된 채로 코드짜기 급급함
아키텍처의 구멍 → 요구 사항 분석 중에 만들어진 아키텍처 약속이 어느 정도 개발이 진행된 후에 작동하지 않는 것으로 밝혀지는 경우

리펙터링

Lava Flow의 명확한 해결책은 딱 하나, 프로덕션 코드 개발에 앞서 건전한 아키텍처를 구축하는 것이다.
만약 아키텍처가 충분히 고려되지 않는다면 중복 코드, 죽은 코드 등 Lava Flow가 지속적으로 발생할 것이다. 그리고 만약 Lava Flow가 존재한다면, 굉장히 고통스럽겠지만, 웬만하면 개발이 지속적으로 이루어지는 상화에서 아키텍쳐의 변화를 주는 것은 좋지 않다.
또한, 차선책으로는 Jenkins SonarQube와 같은 소스코드 분석 툴 혹은 System Discovery를 이용해 불필요한 항목들을 삭제하는 것이다.
그리고, Lava Flow에 의해 만들어진 용암 코드가 좋지 않다는 것은 우리 모두가 알지만 그 코드가 정말 용암인지 확신할 수 없기 때문에 즉시 수정하지는 말아햐 한다.
그렇기 때문에 Lava Flow를 방지하려면 안정적이고 잘 정의되어 있으며 명확하게 문서화된 시스템 수준의 소프트웨어 인터페이스를 구축하는 것이 중요하다. 양질의 소프트웨어 인터페이스에 미리 투자하면 장기적으로 볼 때 굳어진 용암 흐름 코드의 덩어리를 제거하는 데 드는 비용에 비해 큰 이득을 얻을 수 있다.
소규모 연구 단계에서도 Lava Flow는 좋지 않을까?
Lava Flow는 어디서든 좋다고 볼 수는 없지만, 소규모의 프로덕션 환경 전 테스트 용도, 연구 용도로만 쓰이고 버려질 코드라면 Lava Flow를 신경쓰지 않아도 된다.

 Poltergeists

이름: Poltergeists (Gypsy , Proliferation of Classes , and Big DoIt Controller Class)
자주 발생하는 곳: Application
해결책: Ghostbusting
발생 원인: ’아.. 몰랑’와 ‘뭐 정리 좀 안하면 어때’
안티 패턴인 이유: 함수 및 복잡성 관리에 취약
안티패턴의 증거: "I'm not exactly sure what this class does, but it sure is important!” → 나 이거 잘 모르는데, 중요한건 확실해!

Poltergeists 이름의 유래

아크로이드라는 사림이 Gypsy AntiPattern 에 대한 발표를 1996년에 진행했는데, 이 때 Gypsy 계층의 일시적인 출연과 불연속적인 사라짐을 Gypsy Wagon에 비유했다고 한다.
Gypsy Wagon은 하루는 있다가 다음 날 사라지는데, Poltergeists는 'bump-in-the-night types of phenomena'을 일으키는 restless ghosts을 의미한다.
따라서 이 용어가 초기 Gypsy의 '여기 있다가 갑자기 사라지는' 느낌을 유지하면서 이 안티패턴의 '무언가를 일으키기 위해 갑자기 나타나는' 개념을 더 잘 표현할 수 있다고 생각해 등장했다고 한다.

일반적인 Poltergeists의 형태

Poltergeists는 시스템에서 담당할 책임과 역할이 아주 적어 제한적인 클래스로, 그 수명 주기가 매우 짧다. 그렇기에 Poltergeists는 소프트웨어 설계를 복잡하게 만들어 불필요한 추상화를 야기해 지나치게 복잡하고 유지보수도 어렵게 만든다.
이러한 안티패턴은 객체 지향 설계를 처음 접하는 설계자가 아키텍처를 정의하는 경우에 흔히 발생한다고 한다.

Poltergeists의 판단과 영향

무엇을 Poltergeists라 부를 수 있을까?
일시적인 연관관계
Stateless 클래스
임시적이고 짧은 기간의 객체 및 클래스
임시 연결을 통해 다른 클래스를 "시드" 또는 "호출"하기 위해서만 존재하는 단일 연산 클래스
Poltergeists가 어떠한 영향을 끼칠까?
불필요하므로 "표시"될 때마다 리소스를 낭비
여러 개의 중복 탐색 경로를 사용하기 때문에 비효율적
객체 모델을 불필요하게 복잡하게 만들어 적절한 객체 지향 설계를 방해합니다.

Poltergeists의 원인

객체 지향 아키텍처의 부족
객체 지향 접근 방식이 모든 작업에 반드시 적합한 솔루션은 아닙니다. → 한 포스터에 "잘못된 일을 하는 데 올바른 방법은 없다"라는 문구가 적혀 있듯이 말이다. → 즉, 객체 지향이 올바른 도구가 아니라면 이를 구현하는 올바른 방법도 없다는 뜻
부적절한 아키텍처(패턴) 사용

리펙터링

Ghostbusting라는는 것을 사용하는데, Poltergeists 클래스 계층 구조에서 완전히 제거하는 방식으로 폴터가이스트를 해결한다. 하지만 Poltergeists를 제거한 후에는 Poltergeists가 '제공'했던 기능을 대체해야 하는데, 이는 아키텍처를 수정하는 간단한 조정으로 쉽게 해결할 수 있다.