-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,242 @@ | ||
# 09. 설계의 건전성을 해치는 여러 악마 | ||
|
||
# 📌 Contents | ||
|
||
### 📌 데드 코드 | ||
|
||
- 절대로 실행되지 않는 조건 내부에 있는 코드를 데드 코드(dead code) 또는 도달 불가능한 코드(unreachable code)라고 부름 | ||
- 이 악마는 코드의 가독성을 떨어뜨림 | ||
- 또한 언젠가 버그가 될 가능성도 있음. 사양 변경에 의해 좀비처럼 되살아날 수 있음. 이 때 되살아난 코드와 사양이 다르면 버그가 됨 | ||
- 데드 코드는 발견하는 즉시 제거하는 것이 좋음 | ||
|
||
### 📌 YAGNI 원칙 | ||
|
||
- 개발을 할 때 미래를 예측하고, 미리 만들어 두는 경우가 있음 | ||
- 이렇게 미리 구현한 로직은 실제로 거의 사용되지도 않고, 버그의 원인이 되기도 함 | ||
- YANGI라는 소프트웨어 원칙이 있음 | ||
- 'You Aren't Gonna Need it.'의 약자로, '지금 필요 없는 기능을 만들지 말라!'라는 의미 | ||
- 실제로 지금 당장 필요한 것들만 만들라는 방침 | ||
- 소프트웨어에 대한 요구는 매일매일 변함 | ||
- 사양으로 확정되지 않고 명확하게 언어화되지 않은 요구를 미리 예측하고 구현해도, 이러한 예측은 대부분 맞지 않음 | ||
- 예측에 들어맞지 않는 로직은 데드 코드가 됨. 또, 이렇게 만들어진 로직은 일반적으로 복잡함 | ||
|
||
### 📌 매직 넘버 | ||
|
||
- 설명이 없는 숫자는 개발자를 혼란스럽게 만듬 | ||
- 로직 내부에 직접 작성되어 있어서, 의미를 알기 힘든 숫자를 매직 넘버라고 부름 | ||
- 매직 넘버를 사용하지 않으려면, 상수를 활용하면 됨 | ||
- 서비스를 바로 동작시켜 보고 싶을 때, 매직 넘버를 사용하기 쉽지만, 리포지터리에 커밋할 때는 값을 상수로 변경하고 커밋하는 것이 좋음 | ||
|
||
### 📌 문자열 자료형에 대한 집착 | ||
|
||
- 읽어 들인 CSV 파일에서 데이터를 추출할 때, split 메서드를 사용하는 경우가 있음 | ||
- 하지만 그런 용도가 아닌데, 의미가 다른 여러 개의 값을 하나의 String 변수에 무리하게 넣으면, 의미를 알기 어려움 | ||
- 또한 split 메서드 등을 활용하므로 로직이 쓸데없이 복잡해져서 가독성이 크게 저하됨 | ||
- 이는 5장에서 설명했던 기본 자료형에 대한 집착처럼 클래스뿐만 아니라 변수마저 추가하지 않으려는 경향 때문에 이런 코드가 생겨남 | ||
- 의미가 다른 값은 각각 다른 변수에 저장하는 것이 좋음 | ||
|
||
### 📌 전역 변수 | ||
|
||
- 모든 곳에서 접근할 수 있는 변수를 전역 변수라고 부름 | ||
- 자바 언어 사양에는 전역 변수가 없지만, 변수를 public static으로 선언하면, 모든 곳에서 접근할 수 있음 | ||
- 모든 곳에서 참조할 수 있고 조작도 할 수 있는 변수이므로, 어떻게 보면 편리한 기능이라고 생각할 수도 있지만 실제로는 반대임 | ||
|
||
<br /> | ||
|
||
- 여러 로직에서 전역 변수를 참조하고 값을 변경하면, 어디에서 어떤 시점에 값을 변경했는지 파악하기 대단히 힘듭니다. | ||
- 전역 변수를 참조하고 있는 로직을 변경해야 할 때, 해당 변수를 참조하는 다른 로직에서 버그가 발생하는지 검토해야 함 | ||
- 동기화가 필요한 경우에도 문제가 발생함. 동기화는 제대로 설계하지 않으면 락을 얻기 위해 대기하는 시간이 길어져서 퍼포먼스를 크게 떨어뜨림. 또한 동기화 문제가 생기면, 데드락 상태에 빠질 수 있음 | ||
- 전역으로 선언된 변수만이 전역 변수의 '성질'을 갖는 것은 아님 | ||
- 거대 데이터 클래스도 전역 변수와 같은 '성질'을 띠는 경우가 많음 | ||
- 너무 많은 데이터를 가지고 있으므로, 여러 곳에서 참조하기 때문임 | ||
- 또한, 동기화와 관련해서도 거대 데이터 클래스는 전역 변수보다 훨씬 악질적임 | ||
- 동기화를 하고 싶은 인스턴스 변수가 하나뿐이라고 해도, 해당 인스턴스의 다른 인스턴스 변수까지 모두 잠그므로(lock), 성능상 문제가 큼 | ||
- 설계가 제대로 이루어지지 않은 시스템에서 거대 데이터 클래스가 아주 쉽게 만들어짐 | ||
- 전역 변수를 직접적으로 사용하지 않더라도, 전역 변수와 같은 개념을 알게 모르게 사용하고 있는 것임 | ||
|
||
<br /> | ||
|
||
- 전역 변수는 영향 범위가 너무 넓기 떄문에, 여러 곳에서 호출할 수 있는 구조이고, 호출되기 쉬운 구조임 | ||
- 영향 범위가 가능한 한 좁게 설계해야 함. 관계없는 로직에서는 접근할 수 없게 설계해라 | ||
- 전역 변수를 사용하고 싶다면, 정말로 필요한지 검토 필수 | ||
- 전역 변수로 만들기보다는 최대한 한정된 클래스에서만 접근할 수 있는 형태로 설계해라 | ||
|
||
### 📌 null 문제 | ||
|
||
- null이 들어갈 수 있다고 전제하고 로직을 만들면, 모든 곳에서 null 체크를 해야함 | ||
- 결국 null 체크 코드가 너무 많아져서 가독성이 떨어질 것이고, 실수로 null 체크를 안 하는 곳이 생기면 곧바로 버그가 될 것임 | ||
- null은 초기화하지 않은 메모리 영역에서 값을 읽는 것을 피하기 위해 발명된 것임 | ||
- null은 메모리 접근과 관련된 문제를 방지하기 위한 최소한의 구조로서, null 자체가 '잘못된 처리'를 의미함 | ||
- '무언가를 갖고 있지 않은 상태'와 '무언가 설정되지 않은 상태'는 그 자체로 의미가 있는 상태임 | ||
- 이러한 상태조차 존재하지 않음을 뜻하는게 null인데, 이러한 상태들에 null을 사용하면 큰 손실을 불러일으킬 수 있음 | ||
|
||
#### null을 리턴/전달하지 말기 | ||
|
||
- null 체크를 하지 않으려면, 애초에 null을 다루지 않게 만들어야 함 | ||
- null을 리턴하지 않는 설계: 메서드에서 null을 리턴하지 않게 작성하는 것 | ||
- null을 전달하지 않는 설계: 메서드에서 null을 변수에 할당하지 않는 것 | ||
- '무언가를 갖고 있지 않은 상태'나 '무언가 설정되지 않은 상태'를 포함해서 항상 인스턴스가 존재하게 만들면, null 예외가 발생할 걱정을 안해도 됨 | ||
- 따라서 null 체크 자체가 필요하지 않음 | ||
|
||
#### null 안전 | ||
|
||
- null 안전이란 null에 의한 오류가 아예 발생하지 않게 만드는 구조임 | ||
- 일부 프로그래밍 언어는 프로그래밍 언어 자체가 null 안전 사양을 지원하기도 함 | ||
- null 안전을 구현하기 위한 기능으로 null 안전 자료형이 있음 | ||
- null 안전 자료형은 null을 아예 저장할 수 없게 만드는 자료형 | ||
- 코틀린은 기본적으로 모든 자료형에 null 안전 자료형을 사용해서, null을 할당하는 코드는 컴파일조차 되지 않음 | ||
|
||
### 📌 예외를 catch하고서 무시하는 코드 | ||
|
||
- try-catch로 예외를 catch해 놓고도, 별다른 처리를 하고 있지 않는 코드는 굉장히 사악한 로직임 | ||
|
||
#### 원인 분석을 어렵게 만듦 | ||
|
||
- 이러한 코드의 문제는 오류가 나도, 오류를 탐지할 방법이 없어진다는 것임 | ||
- 데이터에 문제가 생기는 등 잘못된 상태에 빠져도, 외부에서는 아무런 문제가 없는 것처럼 보이게 만듦 | ||
- 잘못된 데이터를 이용하다가 또 다른 잘못된 데이터가 만들어질 가능성도 생김 | ||
- 예외를 무시하면, 잘못된 상태를 곧바로 확인할 수 없고 이후 서비스 사용자에 의해 보고될 가능성이 높음 | ||
- 이 때, 어느 시점에 어떤 코드에서 문제가 발생했는지 찾기 힘들어서 코드를 하나하나 확인하면서 원인을 찾아야 해서, 개발자의 시간과 체력을 상당히 많이 고갈시킴 | ||
|
||
#### 문제가 발생했다면 소리치기 | ||
|
||
- 이러한 상황을 피하려면, 잘못된 상태에 어떠한 관용도 베풀어서는 안됨 | ||
- 예외를 확인했다면 곧바로 통지, 기록하는 것이 좋음 | ||
- 가드가 있는 생성자도 잘못된 상태를 막아주는 설계임 | ||
- 문제가 발생하는 즉시 소리쳐서 잘못된 상태를 막는 구조가 좋은 구조임 | ||
|
||
### 📌 설계 질서를 파괴하는 메타 프로그래밍 | ||
|
||
- 프로그램 실행 중에 해당 프로그램 구조 자체를 제어하는 프로그래밍을 메타 프로그래밍이라고 부름 | ||
- 자바에서 메타 프로그래밍을 활용해 클래스 구조를 읽고 쓸 때 리플렉션 API를 사용함 | ||
- 이를 통해 일반적인 프로그래밍에서는 접근할 수 없는 부분까지 접근할 수 있음 | ||
- 그래서 프로그래머들은 메타 프로그래밍을 '흑마법'이라고 표현하기도 함 | ||
- 메타프로그래밍은 용법과 의도를 제대로 이해하지 못하고 사용했을 때, 전체적인 설계를 무너뜨릴 수도 있는 매우 위험한 기술임 | ||
|
||
#### 리플렉션으로 인한 클래스 구조와 값 변경 문제 | ||
|
||
- 리플렉션을 사용하면 final로 지정한 변수의 값도 바꿀 수 있고, private으로 외부에서 접근하지 못하게 만든 변수에도 접근할 수 있음 | ||
- 따라서 리플렉션을 남용하면, '잘못된 상태로부터 클래스를 보호하는 설계'와 '영향 범위를 최대한 좁게 만드는 설계'가 아무런 의미도 갖지 못하게 됨 | ||
- 집을 외부 침입으로부터 안전하게 보호하려 시도는 했지만, 뒷문을 열어 놓은 것과 같음 | ||
|
||
#### 자료형의 장점을 살리지 못하는 하드 코딩 | ||
|
||
- 자바로 대표되는 정적 자료형 언어는 정적 분석으로 정확한 코드 분석이 가능하다는 장점이 있지만, 메타 프로그래밍은 이러한 장점조차 무너뜨림 | ||
- 클래스의 인스턴스를 만들 때는 일반적으로 new 키워드를 사용함 | ||
- 하지만 리플렉션을 사용하면, 메타 정보를 기반으로 인스턴스를 생성할 수 있음 | ||
- generateInstance 메서드는 패키지 이름과 클래스 이름을 문자열로 전달하면, 해당 클래스의 인스턴스를 생성해서 리턴함 | ||
- 이를 활용하면 패키지 이름과 클래스 이름을 문자열로 전달해서, 클래스를 생성할 수 있음 | ||
|
||
<br /> | ||
|
||
- 그런데 IntelliJ IDEA와 같은 IDE에는 클래스와 메서드 등의 이름을 한꺼번에 변경해 주는 기능이 있음 | ||
- 하지만 generateInstance로 만든 인스턴스는 해당하지 않음 | ||
- generateInstance 메서드의 매개변수로 전달된 것이 단순한 문자열이기 때문임 | ||
- IDE의 정적 분석을 사용하면, 어떤 클래스가 어디에서 참조되고 있는지 정확하게 분석할 수 있음 | ||
- 따라서 이름 변경 기능은 이름을 참조하는 모든 곳을 쉽게 일관 변경할 수 있는 것임 | ||
- 하지만 단순하게 문자열로 하드코딩되어 있는 클래스 이름을 그 클래스의 자료형이라고 인식하지 않아서, 이름 변경 대상에서 제외됨 | ||
- IDE의 정적 분석 기능은 이름 변경 이외에도 정의한 위치로 점프, 참조하고 있는 위치 전체 검색 등 개발의 효율성과 정확성 향상에 도움을 주지만, 메타 프로그래밍을 남용하면, 이와 같은 개발 도구의 지원을 받을 수 없게 됨 | ||
|
||
#### 단점을 이해하고 용도를 한정해서 사용하기 | ||
|
||
- 메타 프로그래밍을 사용하면, 뭔가 특별한 능력을 배운 것만 같아 기분이 좋을 수도 있지만, 단점을 이해하지 못하고 사용하면, 유지 보수와 변경이 정말 힘들어짐 | ||
- 마치 '흑마법'처럼 사용할 때는 큰 힘을 얻은 것 같지만, 결국 거대한 악을 불러들여 스스로를 파멸하는 것과 같음 | ||
- 메타 프로그래밍을 사용하고 싶다면 시스템 분석 용도로 한정하거나, 아주 작은 범위에서만 활용하는 등 리스크를 최소화 해야함 | ||
|
||
### 📌 기술 중심 패키징 | ||
|
||
- 패키지를 구분할 때도 폴더를 적절하게 나누지 않으면, 악마를 불러들일 수 있음 | ||
- 구조에 따라 폴더와 패키지를 나누는 것을 기술 중심 패키징이라고 부름 | ||
- 여러 웹 프레임워크에서 MVC 구조를 사용하는데, 이와 같은 형태가 기술 중심 패키징이라고 할 수 있음 | ||
- 프레임워크의 표준적인 구조가 기술 중심 패키징이다 보니, 이에 따라서 폴더 구조를 기술 중심 패키징에 맞게 구성하기 쉬움 | ||
|
||
<br /> | ||
|
||
- 비즈니스 개념을 나타내는 클래스를 비즈니스 클래스라고 함 | ||
- 비즈니스 클래스를 기술 중심 패키징에 따라 폴더를 구분하면, 관련성을 알기 매우 힘들어짐. 파일 단위로 묶여 응집도가 낮아짐 | ||
- 비즈니스 클래스는 관련된 비즈니스 개념을 기준으로 폴더를 구분하는 것이 좋음 (서로 관련있는 클래스들을 한 폴더에 넣어 구분함) | ||
- 그러면 서로 관련없는 클래스끼리 참조할 위험을 방지할 수 있음 | ||
- 또한 관련된 개념끼리 모여 있으므로, 사양이 달라졌을 때 달라진 사양과 관련된 폴더 내부의 파일만 읽으면 되고 관련 파일을 이리저리 찾아 다니지 않아도 됨 | ||
|
||
### 📌 샘플 코드 복사해서 붙여넣기 | ||
|
||
- 프로그래밍 언어와 프레임워크의 공식 사이트에서 샘플 코드를 그대로 복사하고 붙여 넣어 구현하면, 설계 측면에서 좋지 않은 구조가 되기 쉬움 | ||
- 샘플 코드는 어디까지나 언어의 사양과 라이브러리의 기능을 설명하기 위해 작성된 것임 | ||
- 유지 보수성과 변경 용이성까지 생각해서 작성된 코드가 아님 | ||
- '샘플 코드가 이렇게 작성되어 있다'라는 이유로 그대로 구현하면, 순식간에 조악한 로직이 만들어져서 악마를 불러들임 | ||
- 샘플 코드는 어디까지나 참고만 하고, 클래스 구조를 잘 설계해서 사용해라 | ||
|
||
### 📌 은 탄환 | ||
|
||
- 새로운 기술과 방법을 익히면, 곧바로 써 보고 싶어짐 | ||
- 새로운 기술은 개발 현장의 모든 문제를 해결해 줄 것처럼 매력적으로 보이기도 함 | ||
- 하지만 현실에서 발생하는 여러 문제는 특정 기술 하나로 해결할 수 있을 정도로 단순하지 않고, 대부분 매우 복잡하게 얽혀 있음 | ||
- 문제를 해결하기 위해서, 상황을 고려하지 않고 '자신이 알고 있는 편리한 기술'을 활용해 버리면 문제가 해결되기는커녕 반대로 더 심각해 질 수 있음 | ||
- 서양에서 늑대 인간과 악마는 은으로 만들어진 총알로 죽일 수 있다고 알려짐 | ||
- 그래서 어떤 문제를 해결하는 비장의 무기, 묘책을 '은 탄환'이라고 부름 | ||
- 하지만 소프트웨어 개발에는 은 탄환이 없음 | ||
|
||
<br /> | ||
|
||
- 이 책에서 설명하는 방법들은 사양 변경이 있을 때, 이를 조금이라도 쉽게 하기 위한 설계를 설명함 | ||
- 따라서 '실험적인 목적으로 개발한 프로토타입'또는 '사양 변경을 할 필요가 없는 소프트웨어'에 대해서는 큰 효과가 없음 | ||
- 이러한 상황에서 이 책의 설계를 적용한다면, 쓸데없이 설계 비용만 커질 뿐임 | ||
|
||
<br /> | ||
|
||
- 중요한 것은 어떤 문제가 있을 때, 어떤 방법이 해당 문제에 효과적인지, 비용이 더 들지는 않는지 평가하고 판단하는 자세임 | ||
- 문제와 목적을 머릿속에 새겨 두고, 적절한 기술을 선택할 수 있도록 노력하자 | ||
- 설계에 Best라는 것은 없고, 항상 Better를 목표로 할 뿐임 | ||
|
||
# ❓ Questions | ||
|
||
### ❓ 자바스크립트에도 null 안전 자료형이 있나? | ||
|
||
- 코틀린이나 스위프트, 다트에서는 기본적으로 자료형이 null 안전 자료형(null safety)이라 null을 넣을 수 없음 | ||
- null을 넣고 싶다면 자료형 뒤에 `?`를 넣어야 함 | ||
|
||
```kotlin | ||
var nullable: String? = "Hello" | ||
var nonNullable: String = "Hello" | ||
nullable = null // 가능 | ||
nonNullable = null // 오류 발생 | ||
``` | ||
|
||
```swift | ||
var nullable: String? = "Hello" | ||
var nonNullable: String = "Hello" | ||
nullable = nil // 가능 | ||
nonNullable = nil // 오류 발생 | ||
``` | ||
|
||
```dart | ||
String? nullable = "Hello"; | ||
String nonNullable = "Hello"; | ||
nullable = null; // 가능 | ||
nonNullable = null; // 오류 발생 | ||
``` | ||
|
||
- 그럼 자바스크립트에도 이러한 null 연산 자료형이 있거나, null 안전성을 강화할 방법이 있을까? | ||
|
||
<br /> | ||
|
||
- 자바스크립트는 변수에 타입을 지정할 수 없기 때문에 null 안전 자료형이 없는것 같다 | ||
- 또한 타입스크립트에서도 null 안전 자료형은 따로 없는 것 같다. | ||
- 하지만 타입스크립트에 `strictNullChecks` 옵션을 사용하면 null 안전성을 강화할 수 있다고 한다. | ||
|
||
```typescript | ||
// 원래 | ||
let nullable: string = "Hello"; | ||
nullable = null; // 가능 | ||
|
||
// strictNullChecks 옵션 사용 시 | ||
let nullable: string | null = "Hello"; | ||
let nonNullable: string = "Hello"; | ||
nullable = null; // 가능 | ||
nonNullable = null; // 오류 발생 | ||
``` | ||
|
||
### ❓ 자바스크립트의 메타 프로그래밍 | ||
|
||
- 찾아보니 자바스크립트에서 메타 프로그래밍을 사용할 때 Reflect 객체, Proxy 객체 등을 사용할 수 있다고 함 | ||
- 이에 대한 내용은 양이 많고 난이도가 높아보여서 나중에 제대로 한 번 공부해서 정리해 봐야겠다. |