출처 : 박우빈 강사님의 『Readable Code: 읽기 좋은 코드를 작성하는 사고법』
코드를 처음 보는 사람도 직관적으로 동작을 파악하도록 하여, 유지보수성과 확장성을 높이기 위해서이다. 즉, 미래의 시간과 자원을 절약하기 위해서이다.
그렇다면, 어떻게 코드의 가독성을 높일 수 있을까? 바로 코드를 추상화시키는 것이다.
우리는 자연스럽게 일상생활 속에서도 추상과 구체를 통해 대화를 한다.
아래의 예시를 살펴보자.
[추상] 부산 맛집 검색해보자!
[구체] 한반도의 남동단에 자리잡고 있고, 바다에 면한 남쪽을 제외하고는 경상남도와 접하고 있으며, 남으로는 대한해협에 면해 있고, 북으로는 울산광역시와 양산시의 동면과 물금읍, 서로는 김해시의 대동면과 경계를 이루고 있는 곳에 대해 검색 엔진의 웹 크롤러가 크롤링해서 웹 페이지의 내용을 분석하고 중요 키워드나 메타데이터를 추출하여 검색 색인 저장 후 사용자의 검색어와 일치하거나 연관성이 높은 문서를 역색인을 통해 찾고 순위를 매긴 후 뜨는 검색 결과 페이지를 확인해보자.
추상화된 정보를 바탕으로 구체 정보를 유추하고, 대답을 추상화하여 상대에게 전달하면서 대화를 진행한다.
이 과정이 가능한 이유는,
추상화된 정보와 문맥을 통해 구체 정보를 파악할 수 있기 때문이다.
적절한 추상화로 복잡한 데이터와 로직을 단순화하여 읽기 쉽도록 만들자.
이름을 짓는다는 것은 추상적 사고를 기반으로 한다.
- 단수와 복수 구분하기
- 이름 줄이지 않기
- 은어/방언 사용하지 않기
- 좋은 코드를 보고 습득하기
단축키(윈도우, 맥) : shift + f6
한 문단의 주제는 반드시 하나인 것처럼 메서드의 주제는 반드시 하나이다.
메서드 추출을 통해 의미 단위를 명확하게 할 수 있다.
1. 파라미터와 연결지어 풍부한 의미를 전달 가능하다.
ex) makeCountSummaryOf(attendApplications)
2. 파라미터의 타입, 개수, 순서를 통해 의미 전달이 가능하다.
예를 들어, 아래와 같이 String localDate를 파라미터로 넘겨줘야한다면
yyyy-MM-dd를 넘겨줄 것인지, yyyyMMdd를 넘겨줄 것인지, yyyy-MM-dd hh:mm:ss 등 고민이 생길 것이다.
public PersonalAttendApplications getPersonalAttendApplications(String localDate){..}
public PersonalAttendApplications getPersonalAttendApplications(LocalDate searchDate){..}
그러나 파라미터 타입이 LocalDate라면 데이터 형식에 대한 고민없이 바로 넘겨줄 수 있을 것이다.
3. 적절한 타입의 반환이 필요하다.
단축키(윈도우) : ctrl + B
단축키(맥) : option+command+m
추상화 레벨을 비슷하게 맞출 경우, 코드를 이해하기 쉬워진다.
showGameStartComments(); // 10
initalizeGame(); // 10
...
// as-is
if (gameStatus == 1) { // 5
System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!");
break;
}
// to-be
if (doesUserWinTheGame()) { // 10
System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!");
break;
}
as-is 소스의 경우, gameStatus값이 1인 의미에 대해 추론해야하는 과정이 필요하지만
to-be 소스의 경우, 메서드 이름을 통해 바로 유추 가능하다.
유지보수성을 올라가도록 상수를 추출해보자.
상수명을 짓는 규칙은 다음과 같다.
// 대문자 + 언더바
LAND_MINE_COUNTS
단축키(맥) : option+command+c
불필요한 정보들을 기억한 상태로 다른 코드를 읽어야한다면, 위의 모든 정보들을 알아야 코드 분석이 가능하다.
그래서 이번에는 뇌 메모리를 최대한 적게 쓸 수 있는 방법
즉, 인지적 사고를 적게하는 방법에 알아보고자 한다.
위의 주석과 같이 우리는 앞의 조건들을 기억해야 다음 조건들을 생각할 수 있다.
사고체계를 줄일 수 있는 방법이 없을까?
void run(){
if(a == b){ // a와 b가 같을 때
printSame();
} else if(a > 3){ // a와 b가 같지 않으면서 a가 3보다 클 때
printFirstIsBigger();
}
}
빠르게 return을 해줌으로써 앞의 조건들로부터 자유로워졌다!
void run(){
if(a == b){ // a와 b가 같을 때
printSame();
return;
}
if(a > 3){ // a가 3보다 클 때
printFirstIsBigger();
}
}
else 지양을 통해 조건 복잡도를 낮춰주자.
중첩 분기문, 중첩 반복문의 depth를 줄일 수 있는 방법은 무엇일까?
for(int i = 0; i < 10; i++){
for(int j = 0; j < 5; j++){
if(i > j){
... // i < 10, j < 5, i > j
}
주석과 같이 여러 조건들에 대해 인지하고 있어야한다.
메소드 분리를 통해 사고의 depth를 줄일 수 있다.
for(int i = 0; i < 10; i++){
doSomethingWithI(i);
}
private void doSomethingWithI(int i){
for(int j = 0; j < 5; j++){
doSomethingWithIJ(i, j);
}
}
private void doSomethingWithIJ(int i, int j){
if(i > j){
doSomething();
}
}
** 사용할 변수는 가깝게 선언하기
공백을 통해 로직 의미 단위로 나눌 수 있다.
부정 연산자의 경우에도, 사고 로직을 한번 더 거쳐야한다.
긍정을 생각하고 다시 부정으로 변환하는 과정이 필요하기 때문에, 메서드 추상화 방법을 이용하자.
// as-is
if(!(attendApplicationList.size() > 0)){ // (근태 신청서 리스트의 size가 0보다 크다)가 아니다
...
}
// to-be
if(NoAttendApplicationList){ // 근태 신청서가 없을 때
...
}
의도된 예외처리를 통해 예외 메세지를 사용자가 볼 수 있도록 처리하자.