비슷한 추상화 수준으로 작성된 흐름 이란?
흐름에 대한 모든 설명이 비슷한 수준을 가져야 한다는 말이다.
흐름을 설명하는 중간에 갑자기 너무 구체적인 내용이 있으면, 전체흐름을 이해하기 어렵다.
브라우저에 “www.naver.com”을 입력했을 때의 흐름을 설명해 보자.
- www.naver.com이라는 도메인이름을 DNS서버에 보내 IP주소를 조회한다.
- IP 주소를 이용해 HTTP 요청을 보낸다.
- 네이버 서버는 HTTP 요청을 받아서 처리하고, HTML 문서를 포함한 HTTP 응답을 돌려준다.
- HTTP 응답을 받은 브라우저는 HTML 문서를 파싱해 화면에 보여준다.
- HTML 문서를 보여주는데 추가로 필요하 CSS, JS, Image 정보가 있다면 다운로드해 모든 정보를 랜더링 한다.
2번 설명에서 갑자기 “HTTP 요청이 어떻게 이루어지는지 Routing Protocol, packet”을 설명하면 어떨까?
이 설명은 다른 설명과 맞지 않은 추상화 수준이다! 하지만 이건 정확한 정의가 있지 않아 설명하는듯한 흐름을 만들어보자.
적당히 잘 추상화한다는 것.
위 흐름에서 raffle item에 대한 배정작업을 봤을 때 구체적으로 raffle item이 어떻게 배정되는지 알 수 없다. 즉, how에 대한 부분이 추상화되었다고 할 수 있다. 만약 추상화를 하지 않았다면 어떻게 배정하는지에 대한 구체적인 구현로직이 들어간다.
구현 로직이 들어가면 해당 메서드가 하는 일에 대한 흐름이 파악하기 어려워지고 관심도 없는 복잡한 코드 때문에 읽기 어려운 코드가 된다. + 변경하기 어려운 코드가 된다.
추상화를 함으로써 명확하게 흐름을 볼 수 있는 코드가 되었다. ⇒ 여기서 추상화의 의미는 구체적인 그 일을 잘하는 객체한테 위임하겠다는 것이다!
추상화를 하는 것도 중요하지만 “비슷하게”, “적당히 잘” 하는 것이 더 중요하다. 포인트는 “흐름을 잘 보여주는 표현이다” 그래야 설명하듯 읽히는 코드를 만들 수 있다.
이 기준은 사람마다 생각하는 게 다르고 정답이 없다. 가능한 많은 사람이 공감할 수 있는 추상화 수준을 찾기 위해 “지속적으로 노력해야 한다”
너무 과하게 추상화되면 assignmentService, assign(raffle)과 같이 흐름을 보여주기 위한 정보가 부족해지고, 너무 덜 추상화되면 흐름과 상관없는 구체적인 정보로 인해 흐름을 읽기 어려워진다.
“적당히 잘” 추상화하기 위한 Action Item
내가 구현할 로직을 한글로 먼저 작성하고 충분히 설명됐는지 읽어보자.
그리고 그 설명을 코드로 바꾸자. 이때 가능하면 설명 코드가 1:1이 되도록 하고 한글과의 싱크로율이 높도록 만들자.
마지막으로 주석을 지우고 읽어보자.
이때 처음 한글로 작성했을 때와 같이 충분히 설명되는지 읽어보자.
뭔가 잘 안 읽히면 다시 한글부터 시작한다. (Refactoring)
+) 특히 Delegation Service라고도 불리는 Applciation Service에서 이 내용이 굉장히 강조된다.
예측 가능한 코드를 작성하자.
코드를 작성하다 보면 효율적으로 동작하는 코드와 깔끔한 코드 사이에서 고민하는 순간이 종종 있다.
이 트레이드오프 상황에서 사용될 수 있는 게 “ 내 코드가 예측가능한 코드인가”라는 질문이다.
위 두 코드 중 좀 더 예측가능한 코드는 어떤 걸까?
우리는 A라는 기능을 볼 때 그 기능을 떠올렸을 때 혹은 누군가에게 설명할 때의 흐름을 예측하게 된다.
그리고 내가 예측한 대로 코드가 흘러간다면 매우 편하게 코드를 읽을 수 있지만, 예측을 벗어난 코드가 등장하는 순간 “엥? 이 코드는 뭐지?”라고 한번 더 생각하게 된다. 혹은 “여기서는 이런 코드가 있어야 하는데 어딨지?”라는 혼란이 생길 수도 있다.
단 트레이드오프 상황에서 고려해야 할 한 가지 기준일 뿐 절대적인 것은 아니다. 문법상 어쩔 수 없을 수도 있고 성능개선효과가 커서 가독성을 포기할 수도 있다.
예측 가능한 코드를 작성하기 위한 Action Item
작성된 코드를 다른 사람에게 설명해 보자. 설명할 때의 흐름과 코드의 흐름이 같은지 비교해 보자
클린코드 관점도 좋지만 직관적으로 읽히는 코드가 더 좋을 수도 있다.
눈치챘을 수도 있지만, 코드를 작성하고 반복해서 읽어보면서 Bad Smell을 찾아나가는 것은 전체를 관통하는 핵심이다.
부수효과 없는 메서드 만들기
부수효과란? 메서드가 설명하고 있는 행위와 다른 무언가를 추가로 하는 것.
baseballGameFactory.generateWithSize();
위 메서드 설명을 통해 우리는 무엇을 예측할 수 있을까?
⇒ Size를 정해서 게임 객체를 만들어 주겠구나!
부수 효과는 크게 두 가지 문제를 야기한다.
- 이 로직은 어디서 해주고 있는 거야?
- 얘 호출했더니 갑자기 버그 생겼어
객체 사이의 관심사를 분리시키자.
관심사의 분리는 아키텍처에서 주로 언급되는 용어다.
⇒ 복잡한 Application을 한 번에 머리에 담고 개발하는 게 어렵기 때문에 보다 작은 단위로 분리해 문제를 해결해 나가는 것이다. (분할 정복의 일종)
이를 객체지향으로 가져오면 협력하는 두 객체 A, B를 한 번에 머릿속에 그리는 게 아니라, A와 B를 분리해 각각에 집중해 문제를 해결하자는 것이다.
관심사를 명확하게 분리하기 위해 필요한 것은 “캡슐화를 통한 낮은 결합도”이다.
A→B의 방향으로 협력한다고 했을 때 어떻게 관심사가 분리되는지 A, B 객체 입장에서 각각 살펴보자!
- A 객체 입장 : 나는 요청만 보내면 되고 어떻게 요청을 처리하는지 관심 없어! 책임을 가지고 있는 개체니 잘 처리해 주겠지 ㅎㅎ
- B 객체 입장: 누가 나를 호출하는지 관심 없고, 어떻게든 내가 가진 책임을 다해내기만 하면 되겠다!
A객체를 개발할 때는 A객체에만, B객체를 개발할 때는 B객체에만 관심을 집중할 수 있다.
그럼 당연히 개발(문제해결) 난도가 낮아지고, 관심 가질게 적어지기 때문에 코드를 읽기도 쉬워진다.
Action Item( 결국 캡슐화를 하기 위한 것)
협력하는 두 객체 중에서 Client 객체의 코드를 먼저 작성하자. 구체적인 게 결정되지 않은 채로 협력을 만들기 때문에 캡슐화가 잘될 뿐 아니라 협력적인 메서드가 만들어진다.
더욱 확실하게 관심사를 분리하고 싶으면 Client 객체가 추상화 타입을 의존하도록 만들자.
추상화 타입을 의존하면 컴파일 에러 없이 관심사를 분리할 수 있게 된다.
Nullable 한 타입은 읽기 어렵다.
-Nullable 한 타입이 해롭다는 사실은 이미 널리 알려져 있다.
-읽기 쉬운 코드 관점에서 nullable 한 타입의 문제!
⇒ nullable 한 타입은 문맥을 숨긴다. (어떤 상황에 여기 NULL이 들어갈 수 있는지.) 자바에도 Optional이 생겨 null 처리가 가능해졌다.
문맥이 숨겨지면 이 코드를 읽을떼 정보의 공백이 생긴다.
특히 Nullable 한 타입을 변환하는 메서드는 Client객체에게 큰 혼란을 줄 수 있다.
Nullable 한 타입에 대한 문맥을 제공하는 법
- 가능하면 안 쓰는 게 최고!
- Exception, Log Message, Comment 등으로 문맥 작성
- 별도 문서로 기록
Nullable 한 타입 이외에도 문맥을 숨기는 CASE : 매직넘버, 생성자(정적팩토리메서드 권장), 숨겨진 도메인 개념
마무리
전체를 관통하는 Action Item : 코드를 작성하고 여러 번 읽어보자
결국 경험이 쌓여야 한다. 오늘 소개해준 것은 내가 개발해 오면서 가지게 된 기준일 뿐이고, 각자의 기준을 만들었으면 좋겠다.
'JAVA' 카테고리의 다른 글
큰수 만들기 (1) | 2024.10.02 |
---|---|
읽기 쉬운 코드 만들기 with 세션 (1 / 2) (0) | 2024.09.30 |
캡슐화에 대한 정리 with 세션 (0) | 2024.09.26 |
JVM 동작 방식 (0) | 2024.09.20 |
UncheckedException과 CheckedException (1) | 2024.09.13 |
ArrayList는 어떻게 크기가 조절될까? (0) | 2024.09.11 |
Garbage Collection(GC) 더 자세히 살펴보기 (0) | 2024.09.09 |
Java final과 불변성 (0) | 2024.09.08 |