이 장에서 다룰 내용
•
사용하기에 편리하지만 나쁘다고 알려진 습관
•
실제로 유용한 안티패턴
•
모범 사례와 그 반대 사례를 언제 사용할지 확인하는 법
•
안티패턴, 즉 나쁜 관행을 사용한다면 안 좋은 소리를 들을 것이다. 하지만 그렇다고 이를 무조건 멀리해야 한다는 것은 아니다.
•
이번 장에서는 모범 사례보다도 더 도움이 되는 몇 가지 패턴을 검토한다.
•
이렇게 하면서 모범 사례와 우수한 디자인 패턴을 사용해 도움이 되는 경우와 그렇지 않은 경우를 더 잘 이해할 수 있다.
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 또는 로컬 함수를 사용하라.
•
나무와 숲을 구별할 수 없게 만드는 시시하고 중복적인 코드 주석을 피하라.
•
변수와 함수에 알맞은 이름을 지어 그 자체로 설명이 가능한 코드를 작성하라.
•
함수를 소화하기 쉬운 하위 함수로 쪼개 코드를 최대한 설명적으로 유지하라.
•
코드 주석이 유용할 때만 주석을 작성하라.