티스토리 뷰
1. 작게 만들어라!
함수를 만드는 첫 번째 규칙은 '작게!'이고, 두 번째 규칙은 '더 작게!'에요. 하나의 함수는 하나의 역할을 하면서 동시에 명료하게 보여야 해요. 그래서 하나의 함수는 10줄을 넘기지 않도록 노력해야 해요.
if, while과 같은 블록은 한 줄이어야해요. 블록 안의 구조를 새로운 메서드로 추출하면 복잡도와 코드 길이를 낮출 수 있어요. 중첩 구조는 depth 2를 넘어가면 안 돼요. 이렇게 해야 함수의 이해가 쉬워져요.
나의 생각: 이 생각을 간단한 앱을 구현할 때에는 쉽게 지킬 수 있었어요. 당연하고 지켜야하는 이 규칙을 현업에서 코드의 복잡성이 올라가고 구현해야 하는 목표가 복잡해질수록 간과하기 쉬웠던 것 같아요. 새로운 피쳐를 전체 설계할 때에도 힘들 수 있지만 특히 기존 코드에 새로운 기능을 추가할 때 코드를 덕지덕지 붙이기가 쉬운 것 같아요. 복잡할수록 더 필요한 이 규칙을 복잡할 때 지키기 힘들어서 좀 더 습관화할 필요가 있는 것 같아요. 한 메서드에 몇십 줄의 코드가 있는 레거시도 많았기 때문이에요. 개인적으로 이런 리팩터링이 재밌어요.
2. 한 가지만 해라!
함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
여기서 중요한 것은 '한 가지'를 어떻게 정의할 것인지에요. 함수가 정말 하나의 동작만 하는 것을 의미하는지, 아니면 하나의 개념을 수행하는 것인지 정의해야 해요. 그리고 하나의 개념이라면, 그 개념은 어떤 크기인지 정의해야 해요. 책에서 말하는 한 가지란 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행하는 것을 의미해요.
이 말이 와닿지 않는다면 좀 더 직관적인 방법이 있어요. 함수 내에서 어떤 부분을 추출하여 의미 있는 이름을 붙여 다른 함수로 만들 수 있다면 이미 이 함수는 여러 가지 일을 하고 있는 상태에요. 즉, 개발하는 입장에서는 추출할 수 있는 걸 모두 추출한다고 생각하면 될 것 같아요. 물론 이 과정에서 너무 억지스럽게 단순 로직을 메서드화 하는 것은 경계해야 할 것 같아요.
나의 생각: '한 가지'를 정의하는 것이 정말 어려운 것 같아요. 왜냐하면 어떻게 추상화해서 설명하느냐에 따라 모든 메소드는 한 가지로 설명될 수 있다고 생각이 들거든요. 그래서 논리적으로 추출할 수 있는 부분 메서드가 없을 때까지 추출하는 게 좋다고 생각해요. 물론 일이 너무 많아 치일 때에는 따로 이런 고민할 시간을 내기도 힘들더라고요. 그래서 어느 정도 몸에 체득되고 습관화될 필요가 있다고 생각해요.
3. 함수 당 추상화 수준은 하나로!
함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 해요. 한 함수 내에서 추상화 수준을 섞으면 읽는 사람이 헷갈려요.
또 근본 개념과 세부 사항이 섞이기 시작하면 사람들이 함수에 세부사항을 점점 더 추가해요.
4. 위에서 아래로 코드 읽기: 내려가기 규칙
코드는 위에서 아래로 이야기처럼 읽혀야 좋아요. 단순히 컴파일러가 읽는 순서를 의미하는게 아니라 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 오는 게 좋아요. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아지는 게 좋아요. 이걸 저자는 내려가기 규칙이라고해요.
나의 생각: 이 부분에 대한 고민이 많았어요. 하나의 객체가 여러 메서드를 가지고 있을 때
class SomeClass {
func someFuncA() {
self.relatedFuncA()
}
private func relatedFuncA() {
...
}
func someFuncB() {
self.relatedFuncB()
}
private func relatedFuncB() {
...
}
}
순서를 결정하는 것에 고민이 많았어요. 객체(위의 예시에서 someClass)의 입장에서 보면 "중요한 것은 someFuncA와 someFuncB이므로 이게 위에 먼저 나오고 private 한 세부 메서드들이 아래 따로 모아져야하지 않나?"라는 생각이 들었어요. 그런데 메소드 입장에서 보면 someFuncA를 읽을 때 내부에 relatedFuncA라는 메소드가 나오면 자연스레 바로 아래에서 찾게 되더라구요. 단순한 구조일 때에는 어떻게해도 상관 없지만 현업에서 코드가 2000줄이 넘어가는 객체도 있었는데 그런 경우 관련 메소드가 바로 아래에 있지 않으면 찾기 어려웠어요. 그래서 지금은 순서를 A가 먼저 나올지, B가 먼저 나올지 결정하고 그리고 A 안에서 추상화된 메소드 -> 그 메소드 안에서 사용되는 구체화된 메소드 순으로 작성하고 있어요.
5. Switch 문
switch 문은 작게 만들기 어려워요. case가 단 두 개인 switch 문도 저자의 취향에는 길어 단일 블록이나 함수를 선호해요. 또한 한 가지 작업만 하는 switch 문도 만들기 어려워요. 본질적으로 switch 문은 분기 처리를 하고, 자연스럽게 N가지 일을 처리하게 되기 때문이에요.
저자의 경우 switch문은 다형적 객체를 생성하는 경우에만 참는다(?)고 해요.
나의 생각: 이 부분은 다른 언어와 Swift가 좀 다르다고 생각해요. 특히 저는 우테코(우아한 테크 코스) 프리코스에서 switch 문을 사용하지 못하게 교육받았는데 이 섹션과 관계있는 것 같아요. 그래서 처음 Swift를 공부할 때 switch 문에 대한 거부감이 컸어요. switch 문이 코드의 복잡성을 올리는 데에는 동의해요. 하지만 Swift는 Enum의 활용 가치를 높였고 switch 문을 좀 더 적극적으로 활용하는데 열려있다고 생각해요. 현업 코드에서 특히 네트워킹 객체에서 엄청나게 많이 활용되는 경험을 했어요.
단순 분기 처리를 switch 문으로 남발하는 것은 매우 좋지 않고, switch 문 안에 모든 비즈니스 로직을 담는 것은 코드를 매우 읽기 힘들게 하지만, Swift에서는 switch 문을 무조건 지양한다기보다 언제 쓰는 게 적절한 지, 어떻게 써야 case가 많은 경우에도 읽기 쉬운지 고민하는 게 더 중요하다고 생각해요. 물론 저자도 무조건 지양한다고 표현한 것은 아니지만요.
6. 서술적인 이름을 사용하라!
길고 서술적인 이름이 짧고 어려운 이름보다 좋아요.
나의 생각: 특히 Swift는 정말 영어처럼 읽히기 위해 parameter label로 전치사도 많이 써요. 그런데 제가 속했던 팀에서는 영어 전치사가 한 개도 없었어서 쉽게 사용하기가 어렵더라고요. 전체 컨벤션을 해칠 수 있고, 영어가 익숙하지 않은 개발자의 경우 전치사의 존재가 오히려 코드를 읽는 것을 어렵게 할 수 있을 것 같다고 생각했어요. 전치사의 사용 없이 코드를 작성하다 보니 코드에서
func foo() {
self.bar(param: param)
}
처럼 파라미터 이름과 대입되는 변수명이 같은 코드가 많았고 이건 팀 내 컨벤션으로 결정할 사항이라고 생각해요. 개인적으로는 전치사의 활용이 조금 더 좋은 것 같아요.
네이밍의 중요성은 아무리 강조해도 지나치지 않아요! 경험 많은 개발자들 역시 네이밍 고민을 하고, 함께 네이밍을 고민하면서 토론하는 게 굉장히 좋은 경험이었던 것 같아요.
7. 함수 인수
함수의 이상적 parameter의 개수는 0개예요. 다음이 1개, 그다음이 2개예요. 가능한 3개는 피하는 게 좋아요. 4개 이상은 특별한 이유가 필요해요.
나의 생각: 이건 정말 경우에 따라 다르다고 생각해요. 특히 Pure DI를 사용하면서 Factory나 Payload를 등록할 경우 파라미터가 10개가 넘는 경우도 있었어요. 아마 여기에서 말하는 함수는 일반적으로 우리가 어떤 목적을 위해 직접 작성하는 비즈니스 로직을 위한 메서드이겠지만, 큰 범주에서 모든 메서드로 봤을 때 이 규칙을 지키기 힘든 경우는 많다고 생각해요.
다만, 비즈니스 로직에서 인수가 많을수록 테스트를 작성하기 어렵다는 점, 그리고 인수가 없는 함수가 제일 좋다는 점에서는 공감했어요. 인수를 줄이려고 노력하기보다 메서드의 역할을 잘 정의하고 설계하면 자연스레 인수의 개수는 줄어들 수 있을 것 같아요.
8. 플래그 인수
플래그 인수는 추하다. 함술로 부울 값을 넘기는 관례는 정말로 끔찍하다.
함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이기 때문이에요.
나의 생각: 굉장히 공격적인 말에 놀랐어요.. ㅋㅋ 찔렸다고 해야 하나.. 구현해야 했던 작업이 해외에서만 동작하게 해야 해서 서버의 피쳐 플래그를 만들어 분기 처리를 했었는데 프로젝트 코드에서는 이런 코드가 굉장히 많았거든요. 물론 파라미터로 넘기지는 않았어요. FlagManager 같은 식의 객체를 만들어서 직접 관리했어요. 파라미터에 속하진 않았지만 한 메서드 내에서 bool 값을 통해 여러 일을 하게 했다는 점에서는.. 앞으로는 신경 쓰면 더 좋은 부분 같아요.
9. 부수 효과를 일으키지 마라!
부수 효과는 Side Effect라고 표현하는 게 더 와닿는 것 같아요. 많은 경우 시간적인 결합(temporal coupling)이나 순서 종속성(order dependency)을 초래한다고 해요. 즉, 메서드 자체로는 문제가 없어 보이지만 메서드가 다른 메소드나 객체와 시기적으로 연관이 있거나 정해진 순서가 있는 경우 원래 메소드의 목적 이외의 영향을 줄 수 있다는 것이죠. 가장 흔하게 일어나는 경우가 어떤 메소드가 특정 cache를 clear하는 기능을 담고 있을 때 이 메소드 자체에서는 문제가 없지만 순서 문제로 다른 메소드가 뜻하지 않게 계속 빈 cache만 받을 수도 있을 거예요.
10. 명령과 조회를 분리하라!
func set(attribute: String, value: String) -> bool {
...
}
if set(attribute: "username", value: "unclebob") {
...
}
위의 코드에서 set 메서드는 무언가를 성정하는 함수인지, 설정되어 있는지를 확인하는 함수인지 헷갈릴 수 있어요. 의미가 모호하죠. 만약 함수를 구현한 개발자의 의도가 set 함수를 무언가 설정하는 동사형으로 네이밍 한 것이라면 if 문 안에서 set은 형용사처럼 보일 수 있어요. "만약 username이 unclebob으로 설정되어 있다면"으로 해석될 수 있는 거죠.
이런 혼란을 피하기 위해선 아래와 같이 아예 명령과 조회를 분리해야 해요.
if attributeExists(attribute: "username") {
setAttribute(attribute: "username", value: "unclebob")
...
}
11. 오류 코드보다 예외를 사용하라!
코드를 오류로 처리하게 되면 조건문이 발생하기 쉬워요. 그 오류를 개발자가 처리해야 하기 때문이에요. if문으로 코드 블록 depth가 깊어지기 쉽죠. 그래서 try, catch를 사용하면 더 좋아요.
try/catch 블록은 추하다
추한 게 많다고 생각하는 로버트 마틴.. 이렇게 설명한 이유는 try/catch 블록이 발생하면 자연스럽게 정상 작동 코드와 오류 발생 시 코드가 혼재되기 때문이에요. 그래서 이런 경우 내부 로직을 별도 메서드로 뽑아내는 게 보기 좋아요.
12. 반복하지 마라!
중복은 소프트웨어에서 모든 악의 근원이에요. 조금씩 달라 보이는 부분도 고민을 통해 충분히 중복을 줄여나갈 수 있어요.
13. 함수를 어떻게 짜죠?
글을 쓸 때에도 초안은 대개 서투르고 어수선해요. 원하는 대로 읽힐 때까지 말을 다듬고 문장을 고치고 문단을 정리해요. 함수도 마찬가지로 처음에는 길고 복잡하지만 코드를 다듬고, 함수를 만드는 등의 리팩터링 작업을 해요. 이 와중에 항상 단위 테스트는 통과해야 해요.
클린코드(Clean Code) by Robert.C.Martin
'개발 독서' 카테고리의 다른 글
[클린 코드] 6장 - 객체와 자료 구조 (0) | 2021.12.07 |
---|---|
[클린 코드] 5장 - 형식 맞추기 (0) | 2021.12.06 |
[클린 코드] 4장 - 주석 (0) | 2021.12.05 |
[클린 코드] 2장 - 의미 있는 이름 (0) | 2021.12.03 |
[클린 코드] 1장 - 깨끗한 코드 (0) | 2021.12.02 |
- Total
- Today
- Yesterday
- 종이자르기#분할정복#BOJ#Python
- 파이썬알고리즘인터뷰#4장
- django#slicing
- 섬의개수#백준알고리즘#Python
- NumberofDiscIntersections#Codility#Sort#Python
- 토마토#백준알고리즘#Python
- 순열사이클#BOJ#Python
- 텀 프로젝트#백준알고리즘#Python
- Swift#Tuples#Range
- Triangle#Sorting#Codility#Python
- Distinct#Codility#Python
- 쿼드트리#BOJ#분할정복#Python
- django
- API#lazy#
- 반복수열#백준알고리즘#Python
- Brackets#Stacks and Queues#Codility#Python
- 공유기 설치#BOJ#이분탐색#Python
- 나무자르기#BOJ#이분탐색#Python
- 날짜 계산#BOJ#완전탐색#Python
- 터틀비치#리콘#xbox#controller
- PassingCars#Codility#Python
- 랜선자르기#이분탐색#BOJ#Python
- 암호코드#dp#BOJ#Python
- 배열합치기#분할정복#BOJ#Python
- 미로 탐색#백준알고리즘#Python
- filter#isalnum#lower
- N으로 표현#DP#Programmers#Python
- 리모컨#완전탐색#BOJ#Python
- 백준 알고리즘#BackTracking
- 병든 나이트#BOJ#탐욕법#Python
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |