목차
- ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에서 말하고자 하는 바와 다를 수 있습니다.
- 모든 이미지의 출처는 클린 코드(로버트 C. 마틴 저) 책 입니다.
3장 함수
- 길이가 짧고, 이름이 좋고, 체계가 잡힌 함수를 만드는 방법
작게 만들어라!
⚈ 작은 함수가 좋다.
⚈ 이하와 같은 수준으로 줄여야 한다.
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) {
if (isTestPage(pageData)) {
includeSetupAndTeardownPages(pageData, isSuite);
}
return pageData.getHtml();
}
⚈ if 문 / else 문 / while 문 등에 들어가는 블록은 한 줄이어야 한다.
⚈ 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.
한 가지만 해라!
⚈ 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한가지만을 해야 한다.
⚈ 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) {
if (isTestPage(pageData)) {
includeSetupAndTeardownPages(pageData, isSuite);
}
return pageData.getHtml();
}
- 페이지가 테스트 페이지인지 판단한다.
- 그렇다면 설정 페이지와 해제 페이지를 넣는다.
- 페이지를 HTML로 렌더링한다.
- 세 단계로 지정된 함수 이름 아래에서 추상화 수준이 하나다. 의미를 유지하면서 위 코드를 더 이상 줄이기란 불가능하다. 따라서 한 가지 작업만 한다고 볼 수 있다.
함수 당 추상화 수준은 하나로!
⚈ 함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
⚈ 내려가기 규칙
- 코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
Switch 문
⚈ 본직절으로 switch문은 N가지를 처리한다.
⚈ 저자 : 다형적 객체를 생성하는 코드 안에서 사용할 때만 switch 문을 참아준다.
- switch문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법.(다형성 이용)
서술적인 이름을 사용하라!
⚈ "코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다."
- testableHtml (X) -> SetupTeardownIncluder.render (O)
- isTestable (O)
- includesetupAndTeardown (O)
⚈ 이름이 길어도 괜찮다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다.
⚈ 이름을 붙일 때는 일관성이 있어야 한다.
- includeSetupAndTeardownPages
- includeSetupPages
- includeSuiteSetupPage
- includeSetupPage
함수 인수
⚈ 함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개, 다음은 2개. 3개는 가능한 피하는 편이 좋다. 4개 이상은 특별한 이유가 필요하다.
⚈ 출력 인수는 입력 인수보다 이해하기 어렵다. -> ☆ 출력 인수는 쓰지 말자.
- 출력 인수 : 예를들어 return은 void인데, 실은 입력된 인자에 출력을 담는 그런거.
⚈ 많이 쓰는 단항 형식
- 인수에 질문을 던지는 경우 -> boolean fileExists("MyFile")
- 인수를 뭔가로 변환해 결과를 반환하는 경우 -> InputStream fileOpen("MyFile")
⚈ 플래그 인수는 추하다. 함수를 나누는 것이 마땅하다.
⚈ 인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다.
- Circle makeCircle(double x, double y, double radius) -> Circle makeCircle(Point center, double radius)
⚈ 가변인자 (Varagrs)
- e.g. String.format()
- 이 경우 varargs는 하나의 인수로 취급할 수 있다.
⚈ 함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수다.
부수 효과를 일으키지 마라!
⚈ 부수 효과 = side effect
⚈ 함수만 봐서는 한 가지를 하겠다고 생각되는데, 실제론 내부에서 몰래 다른 짓 하지마라.
- checkPassword 라고 지어놓고 내부적으로 Session.initialize() 호출하고 있는 경우 -> 함수가 한 가지만 한다는 규칙을 위반하긴 하지만, 함수명을 checkPasswordAndInitializeSession 이라는 이름이 훨씬 좋다.
명령과 조회를 분리하라!
⚈ 뭔가를 수행하는 명령이거나, 뭔가에 답하는 쿼리 둘 중 하나만 해야 한다.
⚈ ☆ "질문이 답변을 수정해서는 안된다.". 쿼리는 객체의 상태를 변경하지 않기 때문에 순서를 자유롭게 변경할 수 있고, 여러번 호출해도 되게 된다. 명령과 쿼리를 분리함으로써 제한적으로 참조 투명성 혜택을 누리게 된다. (오브젝트 책 중)
오류 코드보다 예외를 사용하라!
⚈ 오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.
⚈ Try/Catch 블록 뽑아내기
- try/catch 블록은 정상 동작과 오류 처리 동작을 뒤섞으므로 별도 함수로 뽑아내는 편이 좋다.
- 함수는 한 가지 작업만 해야 한다. 오류 처리도 한 가지 작업에 속한다.
⚈ 오류 코드를 반환한다는 이야기는 어디선가 오류 코드를 정의한다는 뜻이다. -> 의존성이 생긴다. (오류 코드를 수정하면 그걸 사용하는 모든 클래스를 다시 컴파일해야 한다.)
- 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다. 따라서 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.
반복하지 마라!
⚈ 중복은 소프트웨어에서 모든 악의 근원이다.
구조적 프로그래밍
⚈ 구조적 프로그래밍 원칙
- 함수와 함수 내 모든 블록에 입구와 출구는 하나만 존재해야 한다. (즉, 함수의 return 문은 하나여야 한다.)
- 루프 안에서 break나 continue를 사용해선 안되며, goto는 절대로 안 된다.
⚈ 위 내용은 함수가 클 때만 상당한 이익을 제공한다.
- 함수가 작다면 사용해도 된다.
- goto는 큰 함수에서만 의미가 있으므로 작은 함수에서는 피해야만 한다.
- ☆ 애초에 함수는 작아야 하므로, goto는 절대 쓰면 안되는게 맞고, return문이나 break, continue는 사용해도 된다. 책 읽으면서 goto는 동의하지만 나머진 완전 공감 안되서 무시할까 했었는데 작은 함수에선 의미가 있다고 했고, 애초에 함수는 작아야 한다고 했으므로 저자의 생각과 내 생각이 같아서 다행이었다. 데이크스트라가 말한건 동의 못하지만 dijkstra 알고리즘은 매우 좋아한다.
함수를 어떻게 짜죠?
⚈ 소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다. 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다.
☆ 실습 해봤다.
⚈ 리팩토링 대상이 된 코드는 반년 전쯤 짰던 스프링부트 컨트롤러 코드이다. 로직은 다음과 같다.
- 요청을 기준으로 로그를 기록한다.
- roleChecker를 통해 userInfo(인증 토큰을 통해 획득한 유저 정보)에 그 뒤의 인자들에 해당하는 역할이 있는지 확인한다. 없다면 403 상태를 리턴한다.
- Pager를 사용하는 공통 entity를 생성한다.
- 리스트를 조회한다.
- ResponseDTO에 내용들을 담아 리턴한다. 이 때 logAndGetSame은 응답값에 대한 로그를 쌓고, 자기자신을 다시 리턴하라는 의미로 작성했었다.
- 문제점 : 함수명이 적절하지 못하다. chkRole 만 봐서는 어떤 역할인지 알기 힘들다. logAndGetSame 역시 마찬가지다. 이건 이 책과는 관계 없지만, 시작 시 로그와 리턴 시 로그는 AOP로 빼주면 더 깔끔하다.
⚈ 리팩토링 결과이다. 변경 시 적용된 사항은 다음과 같다.
- 로그 부분은 AOP로 빼줬다. (책 내용과는 관계없다.)
- 역할 확인 함수명을 userInfoHas로 변경해줬다. varargs를 썼으므로 인자는 2개이다.
- 역할이 통과하지 못할 경우 기존엔 boolean으로 리턴해주었으나, 예외로 처리해서 catch에서 403을 리턴해주도록 변경했다.
- list와 전체 글의 수는 하나로 묶어서 객체로 리턴해주도록 했다.
- 애초에 서비스쪽으로 빼서 처리하는게 더 타당하긴 했을 것이다. 그건 스프링부트의 영역인거고, 어쨌든 함수로만 보고 리팩토링 해봤다.
- roleChecker의 경우 사실 DB에 각 API별 권한이 존재했다면 따로 코드로 넣지 않아도 자동으로 처리되게 할 수 있다. 이번엔 리팩토링 대상이 아니었다.
- try/catch 문을 별도의 함수로 빼는 것도 생각해봤는데 굳이 try/catch만을 위해 함수를 하나 더 빼는게 정말 이해하기 더 좋을지 잘 모르겠다. 즉, 추가 로직 없이 단순히 try/catch가 바로 보이지 않게 하려고 동일한 코드를 빼서 함수로 감싸는게 좋은지 모르겠다.
- 추상화 수준이 동일한지 확신이 서지 않는다. "요청에서 역할을 확인한다.", "요청에 해당하는 데이터를 얻는다.", "요청에 응답한다." 정도로 볼 수 있으니 맞는 것 같다. 추가로 더 함수를 써서 빼보려 했는데 오히려 지저분하고 더 이해하기 어렵게 느껴졌다.
'Study > 클린코드' 카테고리의 다른 글
[클린코드] 6장. 객체와 자료 구조 (0) | 2023.01.02 |
---|---|
[클린코드] 5장. 형식 맞추기 (0) | 2023.01.02 |
[클린코드] 4장. 주석 (0) | 2022.12.21 |
[클린코드] 2장. 의미 있는 이름 (0) | 2022.12.14 |
[클린코드] 1장. 깨끗한 코드 (0) | 2022.12.14 |
댓글