Skip to content

Commit

Permalink
update: 09. 설계의 건전성을 해치는 여러 악마.md
Browse files Browse the repository at this point in the history
  • Loading branch information
kiuuon committed May 31, 2024
1 parent 3a073b7 commit 71f92a4
Showing 1 changed file with 240 additions and 0 deletions.
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 객체 등을 사용할 수 있다고 함
- 이에 대한 내용은 양이 많고 난이도가 높아보여서 나중에 제대로 한 번 공부해서 정리해 봐야겠다.

0 comments on commit 71f92a4

Please sign in to comment.