////
Search
3️⃣

3장. 유용한 안티패턴

이 장에서 다룰 내용

사용하기에 편리하지만 나쁘다고 알려진 습관
실제로 유용한 안티패턴
모범 사례와 그 반대 사례를 언제 사용할지 확인하는 법
안티패턴, 즉 나쁜 관행을 사용한다면 안 좋은 소리를 들을 것이다. 하지만 그렇다고 이를 무조건 멀리해야 한다는 것은 아니다.
이번 장에서는 모범 사례보다도 더 도움이 되는 몇 가지 패턴을 검토한다.
이렇게 하면서 모범 사례와 우수한 디자인 패턴을 사용해 도움이 되는 경우와 그렇지 않은 경우를 더 잘 이해할 수 있다.

1. 깨지 않았다면 깨버려라

가급적이면 코드 재작성을 피하는 것이 좋았다. 변경을 거듭할 때마다 잘 되던 것이 오히려 버그로 회귀할 위험이 있기 때문이다.
이렇게 회귀하는 것은 버그가 있는 새로운 기능을 릴리스하는 것보다 더 나쁘다. 즉, 한 단계 더 후퇴하는 것이다.
하지만 코드를 변경하지 않으면 결국 문제가 발생할 수 있다. 새로운 기능을 구현하기 위해서 무언가를 부수고 다시 만들어야 한다면 개발에 방해가 될 수 있기 때문이다.
그래서 가급적, 기존 코드를 그대로 두고 최대한 건드리지 않은 채로 새로운 코드에 모든 것을 추가하는 데 익숙할 수 있다.
하지만 코드를 변경하지 않고 그대로 두면 더 많은 코드가 필요할 수 있으며, 결국 유지보수해야 하는 코드의 양만 증가시킬 뿐이다.
기존 코드를 바꿔야 한다면 이것은 더 큰 문제다. 이는 조용히 넘어갈 수 없다.
기존 코드가 특별한 작업 방식과 밀접하게 연결되어 있다면 수정하는 것 자체가 매우 어려울 수 있다.
이 상황에서 코드를 변경하면 다른 여러 곳을 함께 변경해야 할 수도 있다. 이처럼 변화에 대한 기존 코드의 저항을 코드 경직성이라고 한다.

1. 코드 경직성에 맞서라

코드 경직성에는 여러 요인이 있으며 그중 하나는 코드에 너무 많은 의존성이 있는 경우이다.
의존성은 프레임워크 어셈블리, 외부 라이브러리, 내 코드의 다른 엔티티 등 다양한 것과 연관될 수 있다.
의존성은 왜 문제가 될까? 종속성 체인의 한 요소가 되어버린 이상, 수정을 거칠 때, 부수 효과를 함께 고민해야하기 때문이다.
하지만 보통은 눈에 보이지 않기 때문에 간과하기 쉽다.
A와 B를 의존성으로 묶을 때, A를 변경했다면 B도 변경해야할 수도 있다.

2. 빠르게 옮기고 깨버리자

의존성이 없으면 없을수록 변경하기가 쉽다. 즉, 의존성이 많아지기 전에, 빠르게 깨트리는 것이 좋다.

3. 경계를 존중하라

가장 먼저 가져야할 첫 번째 습관은 의존성에 대한 추상화 경계를 넘지 않는 것이다.
추상화 경계란 코드의 계층 주의에 그려지는 논리적 경계로, 주어진 계층의 관심사 집합을 의미한다.
예를 들어 코드에 웹, 비즈니스, 데이터베이스 계층을 추상화할 수 있다.
코드를 계층화할 때 DB 계층은 웹 계층이나 비즈니스 계층을 몰라야 하며, 웹 계층도 DB 계층을 알아서는 안 된다.
경계를 넘는 것이 왜 좋지 않을까? 그 이유는 바로 추상화의 장점을 없애기 때문이다.
즉, 하위 계층의 구체성을 상위 계층으로 끌어올리면 하위 계층에서 일어나는 모든 변경 사항이 상위 계층에 영향을 미치기 때문이다.
경계를 넘으려는 의존성을 발견하게 된다면 코드를 깨서 동작이 멈추게 한 후, 위반 요소를 제거, 리팩토링해야 한다.
테스트의 중요성
코드 변경으로 어떤 어떤 시나리오가 실패, 성공하게 될지 확인할 수 있어야 한다.
그런 의미에서 테스트는 더 간단하다. 테스트는 지시 사항을 적어 놓은 목록일 수도 자동화된 테스트일 수도 있다.
보통 손이 덜 드는 자동화된 테스트를 선호하며, 테스트 프레임워크를 적절히 사용하여 간단하게 사용할 수 있다.

4. 공통적인 기능을 분리하라

여러 계층에서 사용되어야 하는 공통적인 기능은 모델 계층을 따로 경계를 두어 사용하면 된다.

5. 빚을 지지 마라

기술 부채는 이미 우리가 의식하고 있는 결정이다. 무의식적인 것은 기술적인 미숙함이라고 부른다.
다양한 이유로 기술 부채가 쌓일 수 있는데, 때론 임의의 값을 위해 상수 변수를 만드는 수고를 감당하는 대신 이를 전달하는 것이 더 쉬울 수 있다.
기술 부채를 다루는 가장 좋은 방법은 최대한 선택을 미루는 것이다.

2. 처음부터 다시 작성하라

1. 지우고 다시 써라

새로운 무언가를 개발하는 데 꽉 막힌 느낌이 든다면 처음부터 다시 작성하라.
프로그래밍도 반복할수록 더 잘하게 된다. 처음부터 다시 시작하면 단일 기능을 개발하는 기술이 향상된다. 또한, 일반적으로 향후 작성할 모든 코드에 대한 개발 기술도 향상된다.

3. 코드가 멈추지 않았어도 개선하자

코드의 경직성을 해결하는 방법 중 하나는 코드가 굳지 않도록 계속 휘젓는 것이다.
좋은 코드는 변경하기 쉬워야 하며 필요한 부분을 변경하기 위해 수많은 변경이 뒤따르지 않는 코드이다.
코드 종속성을 업데이트하고 앱을 유동적으로 유지하며 변경하기 어려운 가장 경직된 부분을 정기적으로 확인하는 습관을 만들어두면 좋다.

1. 미래를 향한 경주

의존성을 가지고 있는 라이브러리의 메이저 버전이 업그레이드되는 경우, 변화를 두려워하기 보다 빠르게 업그레이드를 수행하는 것이 좋다.
코드가 손상될 수도 있지만 중요한 것은 이러한 작은 문제를 미리 발생시켜 처리하면 나중에 해결하기 쉬워지기 때문이다.
변화에 대한 저항성이 적을수록, 설계와 유지보수의 용이성 측면에서는 더 좋다.

2. 코드를 깔끔하게 만드는 것은 작성하는 것만큼 중요하다

버그는 어차피 발생한다. 따라서 두려워해서는 안 된다. 어떻게 잘 고칠지가 더 중요하다.
이는 작업 중인 코드를 개선하는 것을 주저하지 말아야 한다는 것을 의미한다.
코드에 변화를 많이 줄수록 코드 변경에 대한 저항력이 줄어들 것이다. 코드를 수정하면 고장이 발생하고, 고장으로 취약한 부분을 인지하게 되며 관리하기 쉽게 만들 수 있기 때문이다.
저자는 이런 종류의 코드 개선 활동을 정원 가꾸기라고 부르며, 새로운 기능을 추가하거나 버그를 수정하지 않더라도 특정한 영역에서 개선되어야 한다고 얘기했다.
개선할 부분이 없어보인다면 코드 분석기의 도움을 받을 수 있다.

4. 스스로 반복하라

복사-붙여넣기 대신 해당 기능을 제공하는 클래스를 만들고, 이를 이곳저곳에서 사용하게 한다면 꽤나 괜찮은 설계처럼 비춰질 것이다.
하지만 한 가지를 간과하게 되는데, 해당 클래스를 사용하게됨으로써 생기는 새로운 의존성 고리가 설계에 영향을 미친다는 것이다.
이런 공유 의존성의 형태는 소프트웨어에서 공유 코드를 사용하는 부분마다 어느정도 차이가 존재하기 때문에 문제가 생긴다.
이런 모듈별 차이는 공유 의존성을 더 복잡하게 만들고 결국 얻는 것보다 잃는게 더 많아진다.

1. 재사용 대 복사

코드를 재사용할 것인지 아님 복사할 것인지, 어떻게 결정할 수 있을까?
이를 결정하는 가장 큰 요인은 호출자의 요청을 어떻게 프레임화할 것인지, 즉 발신자의 요구 사항이 실제로 무엇인지 설명하는 것이다.
기능에게 이름, 관심사 등으로 책임을 부여함으로써 이를 확인할 수 있다.

5. 지금 새로운 것을 시도하라

바퀴를 새로 발명하지 말라는 격언 때문에, 우리는 뭔가를 시도하는 것을 망설이게 될 수 있다. 그러지 말라는 의미다.
일정부분 수용하면서도 새로이 무언가를 만들어내라는 것이다. 우리의 서비스에게 보다 더 적합한 기능은 추상화되어있으므로 직접 만들어야 한다.

6. 상속을 사용하지 마라

장기적으로 볼 때, 상속은 문제 해결보다 더 많은 문제를 야기했다.
다중 상속, 강한 결합이 그 예이다.
대신 합성을 사용하라. 합성은 부모-자식보단 클라이언트-서버 관계에 더 가깝다.

7. 클래스를 사용하지 마라

클래스는 훌륭하다. 그러나 잘못 사용될 경우, 그 영향이 지대하므로 장단점을 잘 파악하고 사용하라

1. 열거형은 맛있다!

하드 코딩의 가장 큰 문제점은 사람이 숫자를 기억하지 못하게 된다는 것이다.
또한 그 의도를 파악하기가 힘들다.
열거형은 문맥을 제공한다. 또한 상수 클래스보다 type-safe하다.

2. 구조체는 아주 좋다!

클래스에는 약간의 스토리지 오버헤드가 있다. 모든 클래스는 인스턴스를 만들 때, 객체 헤더와 가상 메소드 테이브을 저장해야 한다.
또한 클래스는 힙에 할당되며 가비지 컬렉션의 대상이다.
구조체는 경량화된 클래스로 값 타입이므로 스택에 할당된다. 즉 구조체 값을 변수에 할당하는 것은 단일 참조가 해당 변수를 가리키는 것이 아니기 때문에 변수의 내용이 복사됨을 의미한다.
클래스는 할당에서 참조만 복사하기 때문에 크기가 클수록 더 효율적인 스토리지를 제공할 수 있다.
이 모든 것을 생각해 볼 때, 상속이 필요 없는 작고 불변의 값 타입이라면 구조체를 자유롭게 사용하는 게 더 낫다.

8. 불량 코드를 작성하라

모범 사례는 오류로부터 얻어진다. 모범 사례의 관점에서 일부 나쁜 관행은 없어져야할 것으로 지목받고는 한다.

1. If/Else를 사용하지 마라

If/Else 블록은 들여쓰기된 조건부 코드를 만든다.
들여쓰기가 너무 많은 코드는 사람을 혼란스럽게 만들고, 맥락을 방해한다.
가급적 초반에 유효성을 확인하고 최대한 빨리 반환하라, 예외적인 경우를 if 안에 넣고, 우리가 원하는 이상적인 경로를 블록 밖에 놓아라

2. goto를 사용하라

생략

9. 코드 주석을 작성하지 마라

1. 이름을 잘 선택해라

설명이 들어간 이름은 엄청난 장점을 가진다. 가장 중요한 점은 시간을 아낄 수 있다는 것이다.

2. 함수를 활용하라

함수가 작을수록 더 이해하기 쉽다. 함수가 한 화면에 모두 들어가도록 작게 유지하라.
앞뒤로 화면을 스크롤하게 되면 코드가 무엇을 하는지 이해하기가 매우 불편해진다. 함수가 하는 모든 것을 한눈에 볼 수 있어야 한다.
작고 이해하기 쉬운 깨끗한 코드를 작성해야 한다.

10. 요약

논리적 종속성의 경계를 지켜 경직된 코드를 만들지 않도록 하라.
처음부터 다시 작성하는 것을 두려워하지 마라. 다음에 할 때는 이 일을 훨씬 더 빨리 할 수 있을 것이다.
앞길에 방해가 될 수 있는 종속성이 있다면 코드를 부수고 그것을 고쳐라.
코드를 최신 상태로 유지하고 정기적으로 발생하는 문제를 해결하여 스스로를 어려움에 처하지 않도록 하라.
논리적인 책임을 위반하지 않도록 코드를 재사용하는 대신 반복하라.
앞으로 코드 작성에 더 적은 시간이 걸리도록 스마트한 추상화를 개발하라. 추상화를 일종의 투자라고 생각하라.
사용하는 외부 라이브러리가 설계를 좌우하도록 내버려 두지 마라.
코드가 특정 계층에 얽매이지 않도록 하려면 상속보다는 합성을 선호하라.
위에서 아래로 읽기 쉬운 코드 스타일을 유지하도록 노력하라.
함수를 조기에 종료하고 불필요하게 if와 else를 사용하지 마라.
공통적인 코드를 한 곳에 두기 위해 goto 또는 로컬 함수를 사용하라.
나무와 숲을 구별할 수 없게 만드는 시시하고 중복적인 코드 주석을 피하라.
변수와 함수에 알맞은 이름을 지어 그 자체로 설명이 가능한 코드를 작성하라.
함수를 소화하기 쉬운 하위 함수로 쪼개 코드를 최대한 설명적으로 유지하라.
코드 주석이 유용할 때만 주석을 작성하라.