노션 정리
😃 책에서 기억하고 싶은 내용을 써보세요.
작게 만들어라!
- 작은 함수가 좋다.
블록과 들여쓰기
- if 문 / else 문 / while 문 등에 들어가는 블록은 한 줄이어야 한다. 대개 거기서 함수를 호출한다. 그러면 바깥을 감싸는 함수(enclosing function)가 작아질 뿐 아니라, 블록 안에서 호출하는 함수 이름을 적절히 짓는다면, 코드를 이해하기도 쉬워진다.
- 중첩 구조가 생길만큼 함수가 커져서는 안 된다.
한 가지만 해라!
- 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
- 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
- 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.
- 함수는 간단한 *TO 문단으로 기술할 수 있다.
- *TO: LOGO 언어에서 사용하는 키워드 ‘TO’는 루비나 파이썬에서 사용하는 ‘def’와 똑같다. LOGO에서 모든 함수는 키워드 ‘TO’로 시작한다. 이는 함수를 설계하는 방식에 흥미로운 영향을 미쳤다.
- 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다. [G34]
- G34: 함수는 추상화 수준을 한 단계만 내려가야 한다.
- 함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
- 함수 내 섹션
- 한 가지 작업만 하는 함수는 자연스럽게 섹션으로 나누기 어렵다.
함수 당 추상화 수준은 하나로!
- 함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
- 한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다. 특정 표현이 근본 개념인지 아니면 세부사항인지 구분하기 어려운 탓이다.
- 위에서 아래로 코드 읽기: 내려가기 규칙
- 위에서 아래로 프로그램을 읽으면서 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.(내려가기 규칙)
- TO 문단을 읽듯이 프로그램이 읽혀야 한다는 의미다.
Switch 문
- 다형성(polymorphism)을 이용하면 저차원 클래스에 숨기고 절대로 반복하지 않을 수 있다.
- 일반적으로 나는 switch문을 단 한 번만 참아준다. 다형적 객체를 생성하는 코드 안에서다. 이렇게 상속 관계로 숨긴 후에는 절대로 다른 코드에 노출하지 않는다.
서술적인 이름을 사용하라!
- 한가지만 하는 작은 함수에 좋은 이름을 붙인다면 이런 원칙을 달성함에 있어 이미 절반은 성공했다. 함수가 작고 단순할수록 서술적인 이름을 고르기도 쉬워진다.
- 길고 서술적인 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다.
- 함수 이름을 정할 때는 여러 다어가 쉽게 읽히는 명명법을 사용한다. 그런 다음, 여러 단어를 사용해 함수 기능을 잘 표현하는 이름을 선택한다.
함수 인수
- 이상적인 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다. 3개(삼항)는 가능한 피하는 편이 좋다. 4개 이상(다항)은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안 된다.
- 코드를 읽는 사람이 인수를 발견할 때마다 해석해야한다.
- 함수 이름과 인수 사이에 추상화 수준이 다를 수 있다.
- 코드를 읽는 사람이 현 시점에서 별로 중요하지 않은 세부사항을 알아야한다.
- 테스트 관점에서 보면 갖가지 인수 조합으로 함수를 검증 하는 테스트 케이스를 작성할 때 보다 인수가 없을 때 더 간단하다.
- 출력 인수는 입력 인수보다 이해하기 어렵다.
- 많이 쓰는 단항 형식
- 인수에 질문을 던지는 경우. e.g. boolean fileExists(”MyFile”)
- 인수를 뭔가로 변환해 결과를 반환하는 경우.
- e.g. InputStream fileOpen(”MFile”)은 String 형의 파일 이름을 InputStream으로 변환한다.
- 다소 드물게 사용하지만 그래도 아주 유용한 단항 함수 형식이 이벤트다. 이벤트 함수는 입력 인수만 있다. 출력 인수는 없다.
- 이벤트 함수에서 조심해서 사용해야한다. 이벤트라는 사실이 코드에 명확히 드러나야 한다. 그러므로 이름과 문맥을 주의해서 선택한다.
- 변환 함수에서 출력 인수를 사용하면 혼란을 일으킨다. 입력 인수를 변환하는 함수라면 변환 결과는 반환값으로 돌려준다.
- 플래그 인수
- 함수로 부울 값을 넘기는 것은 함수가 여러 가지를 처리한다고 말하는 것과 같다.
- 이항 함수
- writeField(name)는 writeField(outputStream, name)보다 이해하크기 쉽다.
- 좌표계의 경우는 이항 함수가 적절하다.
- 이항 함수가 무조건 나쁘다는 소리는 아니다. 프로그램을 짜다보면 불가피한 경우가 생긴다. 하지만 그만큼 위험이 따른다는 사실을 이해하고 가능하면 단항 함수로 바꾸도록 애써야 한다.
- 삼항 함수
- 삼항 함수를 만들 때는 신중히 고려하라 권고한다.
- 삼항 함수가 필요하다면 순서가 중요하다.
- 인수 객체
- 인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어 본다.
- e.g. Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius);
- 인수 목록
- 때로는 인수 개수가 가변적인 함수도 필요하다.
- 동사와 키워드
- 함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수다.
- 단항 함수는 함수와 인수가 동사/명사 쌍으로 이뤄야 한다.
- 함수에 키워드를 추가하는 방식 assertEquals보다 assertExpectedEqualsActual(expected, actual)이 더 좋다. 그러면 인수 순서를기억할 필요가 없어진다.
부수 효과를 일으키지 마라.
- checkPassword함수에 session.initialize()와 같은 호출은 하지마라 함수 이름만 보고 함수를 호출하는 사용자는 사용자를 인증하면서 기존 세션 정보를 지워버릴 위험에 처한다.
- 만약 시각적인 결합이 필요하다면 함수 이름에 분명히 명시한다.
- 출력 인수
- 일반적으로 우리는 인수를 함수 입력으로 해석한다.
- 객체 지향 언어에서는 출력 인수를 사용할 필요가 거의 없다. 출력 인수로 사용하라고 설계한 변수가 바로 this이기 때문이다.
- 일반적으로 출력 인수는 피해야한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
명령과 조회를 분리하라!
- 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다.
- e.g.
- public boolean set(String attribute, String value); // 변경 전 if (set(”username”, “unclebob”))... ⇒ 동사/형용사의 혼란을 일으킨다. // 변경 후 if (attributeExists(”username”)){ setAttribute(”username”, “unclebob”); ... }
오류 코드보다 예외를 사용하라!
- 명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.
- if (deletePage(page) == E_OK) 위 코드는 동사/형용사 혼란을 일으키지 않는 대신 여러 단계로 중첩되는 코드를 야기한다. 오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야한다는 문제에 부딪힌다.
- Try / Catch 블록 뽑아내기
- 블록을 별도 함수로 뽑아내는 편이 좋다.
- 오류 처리도 한 가지 작업이다.
- 함수는 ‘한 가지’ 작업만 해야 한다. 오류 처리도 ‘한 가지’ 작업에 속한다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
- *Error.java 의존성 자석*
- 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다. 따라서 재컴파일 / 재배치 없이도 새 예외 클래스를 추가할 수 있다.
반복하지 마라!
- 코드가 미묘하게 달라 중복이 금방 드러나지 않더라도 중복은 문제다. 코드 길이를 늘어날 뿐 아니라 알고리즘이 변하면 중복된 코드를 전부 수정해야 한다.
구조적 프로그래밍
- 에츠허르 데이크스트라 (Edsger Dijkstra)의 구조적 프로그래밍 원칙 ⇒ 모든 함수는 return 문이 하나여야 한다. 루프 안에서 break나 continue를 사용해선 안되며 goto는 절대로 안 된다.
- 함수를 작게 만든 다면 간혹 return, break, continue를 여러 차례 사용해도 괜찮다. 오히려 때로는 단일 입 / 출구 규칙(single entry-exit rule)보다 의도를 표현하기 쉬워진다. 반면, goto 문은 큰 함수에서만 의미가 있으므로, 작은 함수에서는 피해야 한다.
함수를 어떻게 짜죠?
- 소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다.
- 함수 짤 때도 마찬가지다. 처음에는 길고 복잡하다. 들여쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다. 그런 다음 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다.
- 처음부터 탁 짜내지 않는다. 그게 가능한 사람은 없으리라.
결론
- 모든 시스템은 특정 응용 분야 시스템을 기술할 목적으로 프로그래머가 설계한 도메인 특화 언어(Domain Specific Language, DSL)로 만들어 진다.
- 함수는 그 언어에서 동사며, 클래스는 명사다.
- 프로그래밍의 기술은 언제나 언어 설계의 기술이다.
- 대가(master) 프로그래머는 시스템을 (구현할) 프로그램이 아니라 (풀어갈) 이야기로 여긴다. 프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 좀 더 표현력이 강한 언어를 만들어 이야기를 풀어간다.
🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.
- 한 번 다 읽더라도 일정한 시간을 두고 계속 정독해야 할 것 같다.
- 이제 한 번 읽고 있지만 코드를 작성하면서 좀 더 많은 부분을 고려할 수 있게 해주었다.
- 나의 가장 안 좋은 습관 중 하나가 코드를 작성하기 전에 너무 많은 부분을 생각해서 시작을 하지 못하는 것이다. ’13. 함수를 어떻게 짜죠?’ 를 읽고 내가 코드를 구현하는 속도가 왜 느린지 알게 되었다.
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- p.47 Switch 문
'독서 > 클린 코드' 카테고리의 다른 글
클린 코드 (Clean Code) - 5장 형식 맞추기 (0) | 2022.02.08 |
---|---|
클린 코드 (Clean Code) - 4장 주석 (0) | 2022.02.07 |
클린 코드 (Clean Code) - 2장 의미 있는 이름 (0) | 2022.01.23 |
클린 코드 (Clean Code) - 1장 깨끗한 코드 (0) | 2022.01.23 |
클린코드 (Clean Code) - 추천사 & 들어가면서 (0) | 2022.01.21 |
댓글