일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |
- 객체지향패러다임
- gRPC
- 프로그래머스
- spring event
- 백준
- docker
- 카카오
- AWS
- 결제서비스
- jwt 표준
- BFS
- branch 전략
- 트랜잭샨
- 깊게 생각해보기
- 구현
- 검색어 추천
- 디버깅
- 코드 계약
- 숫자 블록
- 좋은 코드 나쁜 코드
- 레디스 동시성
- 수신자 대상 다르게
- 알람 시스템
- piplining
- 셀러리
- 이분탐색
- 누적합
- 완전탐색
- prg 패턴
- 쿠키
- Today
- Total
코딩관계론
[Clean code] 함수 본문
작게 만들어라
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
boolean isTestPage = pageData.hasAttribute("Test");
if (isTestPage) {
WikiPage testPage = pageData.getWikiPage();
StringBuffer newPageContent = new StringBuffer();
includeSetupPages(testPage, newPageContent, isSuite);
newPageContent.append(pageData.getContent());
includeTeardownPages(testPage, newPageContent, isSuite);
pageData.setContent(newPageContent.toString());
}
return pageData.getHtml();
}
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
boolean isTestPage = pageData.hasAttribute("Test");
if (isTestPage) {
includeSetupAndTearDownPages(pageData, isSuite);
return pageData.getHtml;
}
if-else, while 등에 들어가는 블록은 한줄이면 가독성이 좋아진다. 함수 이름을 잘 작성한다면
중첨 구조가 생기면 안된다.(1~2단까지는 허용한다).
한 가지만 해라!
위의 첫번째 함수는 페이지를 생성하고, 버퍼를 생성하고 여러가지 작업을 하고 있다. 하지만 옆의 함수는 한가지 작업만 수행한다. (추상화 참조: https://lordofkangs.tistory.com/m/127)
추상화 → 하나의 기능을 하나의 함수로 만드는 것이 추상화이다.
한가지의 정의: 1. 함수 이름 아래에서 추상화 수준이 동일해야한다.
2. 의미 있는 다른 이름으로 작업을 분리할 수 없는 경우
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
boolean isTestPage = pageData.hasAttribute("Test");
if (isTestPage) {
WikiPage testPage = pageData.getWikiPage();
StringBuffer newPageContent = new StringBuffer();
includeSetupPages(testPage, newPageContent, isSuite);
newPageContent.append(pageData.getContent());
includeTeardownPages(testPage, newPageContent, isSuite);
pageData.setContent(newPageContent.toString());
}
return pageData.getHtml();
}
추상화 수준이 두 단계이다
왜냐하면 page를 가져오는 추상화
실제 세부기능을 구현하는 추상화 apppend
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
boolean isTestPage = pageData.hasAttribute("Test");
if (isTestPage) {
includeSetupAndTearDownPages(pageData, isSuite);
return pageData.getHtml;
}
추상화 수준이 한단계다.
하나의 기능을 수행한다.
hasAttribute에 의해서 추상화 수준이 두단계라고 할 수 있지만 위의 문장을 하나의 함수로 빼내서 구현한다고 해도 추상화 수준은 달라지지 않는다
이렇기 때문에 2번 판단법이 존재한다.
추상화 수준을 지키는 법
내려가기 규칙!
추상화 수준이 높은 함수를 코드 윗줄에 두고 추상화 수준이 낮은 함수는 코드를 아래에 둔다
함수 인수
무항이면 가장 좋다 → 이해하기 쉽게 만들고, 테스트를 쉽게 만들기 때문이다.
추상화 수준이 높은 함수를 코드 윗줄에 두고 추상화 수준이 낮은 함수는 코드를 아래에 둔다
단항 형식
boolean fileExists("myFile")
InputStream fileopen("myFile")
boolean openOrDelete(true)
이항 형식
void drawPoint(int y, int x)
writeField(write, outputstream)//bad
단항 인수의 좋은 예제
- 인수에 질문을 하는 형식 → 파일이 열려있니?
- 인수를 변환해 결과를 반환 → string To inputStream
플래그 인수는 피하자 함수가 두 가지 일을 한다고 공표하는 일 → 참이면 A 거짓이면 B
이항 형식의 좋은 예
- 자연적인 순서가 존재할 때
outputStream을 객체로 만들어
→ outputStream.writeField(write)
동사와 키워드
함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름은 필수다. 함수 이름은 동사 인수는 명사 쌍을 이뤄야 한다 . write(name) → 이름을 쓴다.
함수 이름에 키워드를 추가한다. Ex) copy(a, b) → copyAtoB(a, b)
부수 효과(의도하지 않는 영향을 주는 것)를 일으키지 마라
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.initialize();
함수가 맞다면 초기화를 진행하고 있다.
함수가 두가지 일을 수행한다. (비밀번호 체크, 초기화)
checkPassword → checkPasswordAndInit
으로 수정이 필요하다.
명령과 조회를 분리하라
함수는 뭔가를 수행하나 뭔가에 답하거나 둘 중 하나만 해야한다
if(set('username', "bob"))
//separation command and query
if(attrExists('username')){
setAttr('username', 'bob'')
}
독자의 입장에선 if로 인해 username이 bob으로 설정되어 있다면 이렇게 읽힐 수 있다. 따라서 명령과 조회를 분리해야함
오류 코드보다 예외 코드를 사용하라
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
logger.log("page deleted");
} else {
logger.log("configKey not deleted");
}
} else {
logger.log("deleteReference from registry failed");
}
} else {
logger.log("delete failed"); return E_ERROR;
}
public void delete(Page page) {
try {
deletePage(page);
deleteReference(page.name)
deleteKey(name.key)
} catch (Exception e) {
logError(e);
}
}
오류코드를 사용하면 즉시 처리해야 하며 중첩된 코드를 사용하게 된다 하지만 예외 처리를 사용하면 오류 처리 코드가 원래 코드에서 분리되어 코드가 깔끔해진다
public void delete(Page page) {
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
logError(e);
}
}
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());
}
try/catch를 다른 함수로 뽑아내야 하는 이유
1.try/catch는 정상동작과 어류 처리 동작을 뒤섞는다. 따라서 별도의 함수로 구성하는 것이 좋다.
2.오류 처리도 하나의 작업이다.
함수를 어떻게 짜죠?
처음에는 길고 복잡하고, 들여쓰기 단계나 중복된 루프도 많다. 인수목록도 길지만, 이 코드들을 빠짐없이 테스트하는 단위 테스트 케이스도 만들고, 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 처음부터 탁 짜지지는 않는다.
결론: 함수는 그 언어서 동사며, 클래스는 명사다.
'Clean code' 카테고리의 다른 글
파이썬 데코레이터(Decorator) (0) | 2023.02.26 |
---|---|
[Clean code] 클래스 (0) | 2023.01.11 |
[Clean code] 형식 맞추기 (0) | 2023.01.09 |
[Clean code] 자료추상화 (1) | 2023.01.06 |
[Clean Code] 코드에는 의미가 있어야 한다. (0) | 2023.01.03 |