개발자 99% 커뮤니티에서 수다 떨어요!
TIL (Today I Learned)
2022.02.22
오늘 읽은 범위
3장. 함수
책에서 기억하고 싶은 내용을 써보세요.
<aside> 💡 함수의 의도를 명확히 표현해야 읽기 쉬워진다.
</aside>
작게 만들어라 (p. 42)
함수를 만드는 첫째 규칙은 작게!
다.
함수를 만드는 둘째 규칙은 더 작게!
다.
<aside> 💡 예전에 다른 책에서 함수는 딱 한 가지의 기능만 하게끔 작게 쪼개서 만들어라.
라는 말과 일맥상통하는 듯하다.
</aside>
바로 다음 글에 나오네!!
한 가지만 해라 (p. 44)
<aside> 💡 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
</aside>
여기에 나오는 한 가지란?
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행 한다면 그 함수는 한 가지 작업만 하는 것이다.
판단방법 1 : TO (~하려면) 문단 이용(책 예제 해설을 TO문단으로 해석하는 듯)
<aside> 💡 TO RenderPageWithSetupsAndTeardowns, 페이지가 테스트 페이지인지 확인한 후 테스트 페이지라면 설정 페이지와 해제 페이지를 넣는다. 테스트 페이지든 아니든 페이지를 HTML로 렌더링한다.
</aside>
판단 방법 2 : 함수 내에서 의미있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.
그렇다면 그 함수를 다시 분해해서 한 가지 기능만 하도록 고쳐라.
함수 당 추상화 수준은 하나로! (p.45)
함수 내 모든 문장의 추상화 수준이 동일해야 한다.
내려가기 규칙
한 함수 다음에 나오는 함수는 추상화 수준이 한 단계 낮은 함수가 와야 한다. 즉 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
Switch 문 (p. 47)
switch 문 자체가 여러 가지 일을 분기시켜 처리 하므로, 한 가지 작업만 하도록 만들기 어렵다. 하지만 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법이 있다. (다형성 - polymorphism -이용)
필자도 이야기 했지만 뜻대로 되지 않는 경우가 많을 것이다. 개인적으로 최대한 switch 문을 피한다.
서술적인 이름을 사용하라! (p. 49)
이름을 정하느라 시간을 들여도 괜찮다. 이런저런 이름을 넣어 코드를 읽어보면 더 좋다. (최신 IDE에서 리펙토링이 쉬우므로 최대한 서술적인 이름을 골라라.)
이름을 붙일 때는 일관성이 있어야 한다. 모듈 내에서 함수 이름은 같은 무누구, 명사, 동사를 사용한다.
함수 인수 (p. 50)
함수에서 이상적인 인수의 개수는 0개 이다. 그 다음은 1개이다. 3개 이상은 쓰지마라. 즉 적을 수록 좋다.
부수 효과를 일으키지 마라! (p. 54)
하나의 기능을 처리하는 함수에 다른 기능을 넣지 마라. 만약 꼭 필요하다면 함수 이름을 수정하여 부가적인 기능이 처리됨을 알려라.
일반적으로 출력 인수는 피해야 한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경하는 방식을 택하라.
appendFooter(report); → report.appendFooterr()
명령과 조회를 분리하라! (p. 56)
함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나 여야만 한다.
객체 상태를 변경하거나, 객체 정보를 반환하거나!
애매모호한 형태의 함수를 사용하지 말 것
if (set(”username”, “unclebob”)) 은 username에 unclebob을 셋팅하라는 건지, username에 unclebob이 셋팅이 되어 있다면 인지 헷갈린다.
if (attributeExists(”username”)) { setAttribute(”username”, “unclebob”); ....} 처럼 명령과 조회를 분리하면 된다.
오류 코드보다 예외를 사용하라! (p. 57)
try {} catch {} 구문을 사용하라. 단 별도의 함수로 만들어 정상 동작과 오류 처리 동작을 분리하라.
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다. 따라서 재컴파일/재배치 없이도 새 예외 클래스르 추가할 수 있다.
반복(중복)하지 마라. (p. 60) - Don’t Repeat Yourself (DRY 법칙)
중복은 소프트웨어에서 모든 악의 근원이다.
소스코드에서 중복을 제거하려는 지속적인 노력을 기울여라.
구조적 프로그래밍 (p. 61)
꼭 필요한 경우가 아니라면 사용하지 말 것. 특히 goto
그래서 함수를 어떻게 짜죠? (p. 61) - 지은이의 코딩 방법
처음에는 길고 복잡하다. 중복된 루프도 많고, 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 단위 테스트 케이스는 모두 만든다.
코드를 리펙토링 한다.
이름을 바꾸고, 중복을 제거하고, 메서드도 줄이고 순서도 바꾼다.
때로는 전체 클래스를 쪼개기도 한다.
이 와중에도 코드는 항상 단위 테스트를 통과한다.
최종적으로 이 장에서 설명한 규칙을 따르는 함수가 얻어진다.
처음부터 탁 하고 튀어나오지는 않단다.
그게 가능한 사람은 없으리라.
오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요
다시금 강조되는 “코드는 이야기처럼 읽혀야 좋다.” 라는 것
그나마 다행인 것은 함수는 대부분 한 가지 기능만 하도록 짜고 있다는 것이다. (그래도 그렇게 안 되는 것들도 많다는 것에 안타까움을 느깐다.) 다만 함수명이 통일되지 못하고, 인수가 조금 많은 경우가 있다는 것이다.
변수명, 함수명, 클래스 명등 항상 이름 짓기가 많이 힘들다는 것을 느끼고 있고, 실무에서 항상 생각하며 바꿀 수 있도록 몇 번이고 읽어야 할 것 같다. 조금씩 고쳐 나가다 보면 언젠가는 제대로 된 코딩을 하고 있지 않을까?
궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
함수를 만드는 이유 - 한 가지만 해라 중에서
큰 개념을 (다시 말해, 함수 이름을) 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서이다. (뭔 말이냐???)
SRP (Single Responsibility Principle - 단일 책임 원칙)
클래스는 단 한 개의 책임을 가져야 한다.
OCP (Open-Closed Principle - 개방-폐쇄 원칙)
확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
하나의 객체가 여러 가지 타입을 가질 수 있는 것
테스트는 해 보질 않아 assertEquals 같은 함수는 잘 모르겠다. 언젠가는 써 봐야지...