Categories
Algorithm🧩
백준 📝
BookReview📕
CleanCode✨
Network 📨
Database 🗄
DevOps☁️
에러 일기📕
Etc💬
Fishy Fish 🎣
Spring🌱
[클린 코드] 함수
함수
작게 만들어라
함수를 규칙은 작게 만드는 것
- if문, while문 등에 들어가는 블록은 한 줄
- 이 블록에서 다른 함수를 부른다.
- 함수의 이름이 적절하다면 이해하기 훨씬 쉬워진다.
중첩 구조가 생길만큼 함수가 커지면 안된다는 의미
한 가지만 해라
함수는 한가지만을 해야한다.
- 그 한가지를 잘해야 한다.
- 예시 코드)
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception { if (isTestPage(pageData)) includeSetupAndTeardownPages(pageData, isSuite); return pageData.getHtml(); }- 페이지가 테스트 페이지인지 판단
- 설정 페이지와 해제 페이지를 넣는다.
- 페이지를 HTML로 렌더링한다.
- 지정된 함수 이름 아래 추상화 수준이 하나이다.
- 의미를 유지하며 해당 코드를 더 줄이기는 불가능
- 다양한 섹션으로 나누기 어려울 때까지 나누어야 한다.
함수 당 추상화 수준은 하나로
함수가 한 가지 작업만 하기 위해선 함수 내 모든 문장의 추상화 수준이 동일해야함
- 한 코드 내에서 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다.
- 위에서 아래로 이야기처럼 읽히는 코드가 좋다:
내려가기규칙
서술적인 이름을 사용하라
코드를 읽으며 짐작한 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드로 불러도 된다.
- 한 가지만 하는 작은 함수에 좋은 이름을 붙이면 이 원칙을 달성할 수 있다.
- 이름이 길어도 짧고 어려운 이름보다는 낫다.
- 서술적인 이름을 사용한다면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
- 모듈내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
함수 인수
가장 이상적인 인수 개수는 0개
- 4개 이상일 경우는 특별한 이유가 있어도 사용하지 않는 것이 좋다.
단항형식의 인수
함수에 인수를 1개 넘기는 경우
- 인수에 질문을 던지는 경우
- 인수를 뭔가로 변환해 반환하는 경우
- 이벤트 함수: 함수 호출을 이벤트로 해석해 입력 인수로 시스템 상태를 바꾸는 경우
- 이경우가 아니라면 단항 함수는 가급적 피한다.
플래그 인수
함수로 bool값을 넘기는건 너무 추하다.
- 하지말자.
이항 함수
이항함수가 적절한 경우
- 좌표계의 점을 넘기는 경우
- 가능하면 단항함수로 바꾸어야 한다.
삼항 함수
신중히 고려해야 한다.
인수 객체
인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다.
- 좌표계의 점 x, y를 따로 넘기는 것 보다 Point 객체로 넘기는 것이 낫다.
인수 목록
인수 개수가 가변적인 함수가 필요할 경우가 있다.
- String.Format
동사와 키워드
함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수
- 단항 함수는 함수와 인수가 동사/명사와 쌍을 이루어야 한다.
- 함수 이름에 키워드(인수 이름)을 추가해 인수 순서를 기억할 필요가 없도록 한다.
부수 효과를 일으키지 마라
한 함수에서는 딱 한가지만 수행하도록 해야한다.
-
예시
public class UserValidator { private Cryptographer cryptographer; public boolean checkPassword(String userName, String password) { User user = UserGateway.findByName(userName); if (user != User.NULL) { String codedPhrase = user.getPhraseEncodedByPassword(); String phrase = cryptographer.decrypt(codedPhrase, password); if ("Valid Password".equals(phrase)) { Session.initialize(); return true; } } return false; } } -
Session.initalize(): 함수가 일으키는 부수효과 - 의도치 않게 세션 정보가 날아갈 수도 있다.
출력 인수
출력인수는 일반적으로 피하는 것이 좋다.
- 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
명령과 조회를 분리하라
함수는 뭔가를 수행하거나 답하거나 둘 중 하나만 해야 한다.
- 객체 상태를 변경하거나 객체 정보를 반환하거나
- 둘 다 하면 혼란을 초래한다.
오류코드보다 예외를 사용하라
명령 함수에서 오류 코드를 반환하는 것은 명령/조회 분리 규칙을 미묘하게 위반한다
- ex) if(deletePage(page) == E_OK)
- 여러 단계로 중첩되는 코드를 야기
- 오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야 한다는 문제
Try/Catch 블록 뽑아내기
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()); }
오류 처리도 한 가지 작업
오류처리 하는 함수는 오류만 처리해야 한다.
반복하지 마라
중복은 소프트웨에에서 모든 악의 근원이다.
- 만은 원칙과 기법이 중복을 없애거나 제어할 목적으로 나왔다.
- 구조적 프로그래밍, AOP, COP 모두 어떤 면에서 중복 제거 전략 중 하나
구조적 프로그래밍
모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다.
- 함수는 return 문이 하나여야 한다.
- 루프 안에서 break, continue, goto를 사용하면 안된다.
- 하지만 함수가 작다면 return, break, continue를 여러차례 사용해도 괜찮다.
함수를 어떻게 짜죠?
처음에는 길고 복잡하며 들여쓰기 단계도 많고 중복된 루프도 많다.
- 이 코드를 빠짐없이 테스트하는 단위 테스트 케이스를 만든다.
- 이후 코드를 다듬고 함수를 만들고 이름으 바꾸고 중복을 제거한다.
- 메서드를 줄이고 순서를 바꾼다.
- 전체 클래스를 쪼개기도 한다.
- 이 와중에 코드는 항상 단위 테스트를 통과해야 한다.
처음부터 바로 짜지지 않는 것이 정상이다..