Book Review

[읽기 좋은 코드가 좋은 코드다] 책 리뷰

빈코 2022. 6. 14. 18:00

회사에 갓 입사했을 때 매주 금요일마다 프로그래밍에 관한 주제를 가지고 발표하는 시간을 가졌었다. 하지만, 얼마 지나지 않아 프로젝트를 바쁘게 진행하다 보니 매주 하던 PT(Presentation)를 진행하지 못하게 되었다. 많은 아쉬움이 남아서 동기와 스터디를 형성하여 격주로 책을 한 권 선정하고 스터디를 시작하게 되었다. 나의 첫 스터디 주제로 '읽기 좋은 코드가 좋은 코드다' 책을 선정하게 되었다.

 

책 이미지
지은이:더스틴 보즈웰, 트레버 파우커

 

스터디에 관한 포스팅은 기존 포스팅 방식과 다르게 대화하는 방식으로 진행 할 예정이다. 이전에 회사에서 PT 했던 방식들은 대게 발표하는 방식으로 진행되었지만, 스터디는 Q&A와 토론하는 과정이 있으면 좋을 것 같아서 정해진 형식 없이 자유롭게 자료를 준비하기로 하였다😄

 

읽기 좋은 코드?

 

할머니에게 설명할 수 없다면 당신은 제대로 이해한 게 아닙니다
- 알버트 아인슈타인

 

복잡한 생각을 다른 사람에게 설명할 때 중요하지 않은 자세한 내용 때문에 듣는 사람을 혼동시키는 일이 종종 있다. '쉬운 말'로 자신의 생각을 지식이 부족한 사람에게 전달하는 기술은 매우 소중하다. 여기에는 설명할 내용을 걸러서 요지만 뽑아내는 능력이 요구된다. 이는 듣는 사람이 내용을 잘 이해하게 도울 뿐만 아니라 설명하는 사람 자신도 그 내용을 다시 한번 명확하게 이해하게 도와준다. 

 

지금 내가 포스팅 하고 있는 블로그도 마찬가지인 것 같다. 주제를 선정하고 주제에 관한 정보와 이야기를 나만의 그릇에 감싸서 독자분들이 쉽게 이해할 수 있도록 노력을 하고 있다. 그렇다면 프로그래밍 세계에서는 이런 관점이 어떻게 적용될까?

 

개발자가 작성하는 코드는 위에서 예시 들었던 블로그와 마찬가지로 다른 사람에게 보여줄 때 같은 기술을 사용해야 한다. 우리는 프로그램이 수행하는 일을 설명해주는 가장 주된 방법이 결국 소스코드 이기 때문에, 코드를 더 명확하고 간단하게 만들 줄 알아야 한다.

 

표면적 수준에서의 개선

책의 1부에서는 표면적인 수준에서의 코드 개선에 대해 다룬다. 이름 짓기, 설명문, 미학, 코드베이스 등의 모든 줄에 적용될 수 있는 간단한 조언들을 담았다. 

 

이름에 정보를 담아내라🌟

변수, 함수, 혹은 클래스 등의 이름을 결정할 때는 항상 같은 원리가 적용된다. 이름을 일종의 설명문으로 간주해야 한다. 충분한 공간은 아니지만, 좋은 이름을 선택하면 생각보다 많은 정보를 전달할 수 있다. 다음은 책에서 소개된 6가지의 방법이다.

 

  • 특정한 단어 고르기.
  • 보편적인 이름 피하기 (혹은 언제 그런 이름을 사용해야 하는지 깨닫기)
  • 추상적인 이름 대신 구체적인 이름 사용하기.
  • 접두사 혹은 접미사로 이름에 추가적인 정보 덧붙이기.
  • 이름이 얼마나 길어져도 좋은지 결정하기.
  • 추가적인 정보를 담을 수 있게 이름 구성하기.

 

사실상 6가지의 주제를 보았을 때, 단순히 텍스트로 보기 때문에 크게 와닿지는 않는다. 우리는 개발자니깐 코드 예시를 보면서 Q&A를 하려 한다.

 


Q&A🚨

1. 루프 반복자

코드를 보고 오류를 찾아보자!

for(int i = 0; i < clubs.size(); ++i){
	for(int j = 0; j < clubs[i].members.size(); ++j){
    		for(int k = 0; k < user.size(); ++k){
            		if(clubs[i].members[k] == users[j]){
                		...Business logic	
               	 	}
       	 	}
    	}
}

 

실무에서 3중 반복문을 사용하는 일이 많지는 않지만, 위의 코드를 보았을 때 한눈에 오류를 파악할 수 있나? 생각보다 오류를 찾기 어려울 것이다.

 

오류는 if 구문에서 members []와 users []는 잘못된 인덱스를 사용하고 있었다. 이러한 종류의 버그는 코드를 따로 떼어놓고 보면 잘못된 게 없어 보여서 좀처럼 찾기 힘들다. 이럴 때는 더 명확한 의미를 드러내는 이름을 사용하면 도움이 된다.

 

(i, j, k) 같은 인덱스를 사용해서 루프를 반복하는 대신 (club_i, members_i, users_i) 혹은 간결하게 (ci, mi, ui) 같은 이름을 사용하여 위와 같은 버그를 좀 더 쉽게 찾아낼 수 있어야 한다.

 

if (clubs[ci].members[ui] == users[mi]) # 버그! 처음 문자가 다르다.

 

2. 단위를 포함하는 값들

코드를 보고 더 나은 변수명을 생각해보자. 상황은 웹페이지 로딩하는 시간을 측정하는 상황이다.

 

int start = (new Date()).getTime(); //Page 맨 위
int elapsed = (new Date()).getTime() - start; //Page 맨 아래

 

이 코드는 특별한 오류를 발생시키지는 않지만, getTime() 메서드가 반환해주는 단위를 변수명에 포함시켜주는 것이 좋다. 추후에 start라는 변수명을 봤을 때, getTime() 메서드의 반환 단위를 알아도 한번 더 생각해야만 하고, 만약 메서드를 모른다면 start와 elpased에 담겨 있는 값을 직접 디버깅해보는 수밖에 없다.

 

int start_ms = (new Date()).getTime(); //Page 맨 위
int elapsed_ms = (new Date()).getTime() - start; //Page 맨 아래

 

변수에 _ms를 추가해서 모든 문제를 명확하게 해주는 친절함이 필요하다. 이러한 친절함은 타인에게뿐만 아니라, 미래의 내가 다시 이 코드 소스를 볼 때에도 적용이 된다.

 

⭐이 밖에도, 본인이 지은 이름을 "다른 사람들이 다른 의미로 해석할 수 있을까?"라는 질문을 던져보며 철저하게 사용한 이름을 확인해 볼 필요성이 있다.

 

 

주석에 담아야 하는 대상📲

코드를 작성할 때, 작성자의 머릿속에는 많은 귀중한 정보가 있다. 하지만 그 정보를 알기 쉽게 작성해 놓지 않는다면 다른 사람이 그 코드를 읽었을 때 그러한 정보는 알 수가 없다. 그들이 가질 수 있는 정보라곤 눈앞에 코드뿐인 것이다. 주석은 이러한 귀중한 정보를 담아낼 수 있다. 먼저 주석에 담지 말아야 할 대상을 먼저 알아보자. 

 

1. 주석에 담지 말아야 할 대상

나쁜 이름에 주석을 다는 행위보다 이름을 고치는 것이 현명하다. 예시를 바로 살펴보자😀

 

// 반환되는 항목의 수나 전체 바이트 수와 같이
// Request가 정하는 대로 Reply에 일정한 한계를 적용한다.
void CleanReply(Request request, Reply reply);

 

언뜻 보면, 상당히 친절한 개발자가 자신의 의도를 다 적어준 것으로 판단할 수 있다. 하지만 주석최소한으로 하는 것이 좋으며, "한계를 적용한다"는 부분은 애초에 함수명에 포함시켜서 이름에 역할을 더 실어주는 것이 좋다. 좋은 이름은 함수가 사용되는 모든 곳에서 드러나므로 좋은 주석보다 더 낫기 때문이다.

 

// 'reply'가 count/byte 등과 같이 'request'가 정하는 한계조건을 만족시키도록 한다.
void EnforceLimitsFromRequest(Request request, Reply reply);

 

위와 같이, 함수명이 전보다 더 스스로 설명하는 느낌을 주는 것이 좋다.

 

2. 주석에 담아야 하는 대상

2-1. 생각을 기록하라.

좋은 주석은 단순히 '자신의 생각을 기록하는 것' 만으로도 탄생할 수 있다. 즉, 코딩할 때 생각했던 중요한 생각을 기록하면 된다. 한 가지 예를 들면, 영화에는 종종 영화 제작자들이 자신의 통찰을 설명하고, 영화가 만들어진 과정을 관람객이 잘 이해하게 도와주는 '감독의 설명'을 담은 트랙이 있다. 이와 비슷한 방식으로 중요한 통찰을 기록한 주석을 코드에 포함시켜야 한다.

 

2-2. 코드에 있는 결함을 설명하라.

코드는 지속적으로 진화하며, 그러는 과정 중에 버그를 갖게 될 수밖에 없다. 아래 표는 프로그래머들 사이에서 널리 사용되는 표기법들이다.

 

표시 보통의 의미
TODO: 아직 하지 않은 일 / 대소문자 구별하여 강도를 다르게 사용하기도 함.
FIXME: 오동작을 일으킨다고 알려주는 주석
HACK: 아름답지 않은 해결책
XXX: 위험! 여기 큰 문제가 있다.

 

 

루프와 논리를 단순화하기

책의 2부에서는 루프와 논리를 단순화하는 과정에 대해 다룬다. 프로그램에서 사용되는 루프, 논리, 변수를 개선하여 더 이해하기 쉽게 만드는 방법에 관해 이야기한다.

 

읽기 쉽게 흐름 제어 만들기🚳

흐름을 제어하는 조건과 루프 그리고 여타 요소를 최대한 자연스럽게 만들도록 노력해야 한다. 또한 코드를 읽다가 다시 되돌아가서 코드를 읽지 않아도 되게끔 만들어야 한다.

 

1. (while (bytes_received < bytes_expected)) 같은 비교 구문을 작성할 때는, 변화하는 값을 왼쪽에 놓고 안정적인 값을 오른쪽에 놓는 편이 좋다.

2. if/else 문의 블록 순서도 일반적으로 긍정적이고, 쉽고, 흥미로운 경우를 앞에 놓도록 하는 편이 좋다

3. 삼항연산자는 종종 코드의 가독성을 떨어트릴 수 있어서 유의해야 한다.

 

중첩을 최소화 하자

코드의 중첩이 심할수록 이해하기 어렵다. 코드를 읽다가 닫는 괄호 ( } , ] )를 만나면 스택의 값을 꺼내고 그 아래 적용된 조건을 기억하는 일이 더 어려워지기 때문이다. 아래의 예시 코드를 보자

 

if (user_result == SUCCESS) {
   if (permission_result != SUCCESS) {
    	System.out.println("error reading permissions");
    	return;
    }
}

 

2번의 조건문이 중첩되어 읽기에 깔끔하지 못한 코드다. 이 코드를 처음 보는 개발자라면 조건 문안에 조건문을 생각하고 또 괄호 끝날 때마다 값을 꺼내고 기억하고 있어야 하는 번거로움이 있다. 이럴 때는, 코드가 길어져도 중첩을 피하는 것이 좋다.

 

if(user_result != SUCCESS){
	System.out.println("fail_user_result");
	return;
}

if(permission_result != SUCCESS){
	System.out.println("fail_permission_result");
    return;
}

 

이 밖에도, 코드를 수정해야 하는 상황이 온다면 코드를 새로운 관점에서 바라보는 시각이 필요하고 뒤로 한걸음 물러서서 코드 전체를 보는 시각 또한 필요하다.

 

변수와 가독성📖

프로그램에서 사용되는 변수는 상당히 빠르게 늘어난다. 그렇기 때문에 변수 하나하나를 기억하는 일이 얼마나 힘든지에 대해 모든 개발자가 공감할 것이다. 변수를 최대한 덜 사용하는 것이 좋지만, 그렇지 못할 상황에는 기억하는 시간을 짧게 코딩하는 것이 중요하다. 대표적으로 변수의 위치 조정 있다.

 

public Map<String, Object> selectItemList(int pageNum, int pageRowCount, String searchType) {
	Map<String, Object> param = new HashMap<>(); // 1
        Map<String, Object> resultMap = new HashMap<>(); // 2
        List<HomePageHistoryStatisticsVO> voList = new ArrayList<>(); // 3
        
        param.put("pageRowCount", pageRowCount); // 1
        param.put("pageNum", pageNum); // 1
        param.put("searchType", searchType); // 1
        
        List<ItemEntity> entityList = dao.selectItemList(param); // 1
        PagingInfoVO pagingInfo = new PagingInfoVO(entityList, pageRowCount); // 4
        for(ItemEntity entity : entityList){
            voList.add(entity.itemVO());
        }
        
        resultMap.put("data", voList); // 2,3
        resultMap.put("pagingInfo", pagingInfo); // 2,4
        return resultMap;
    }

 

위의 코드 예시가 어떻게 동작되는지에 관한 설명은 주제와 맞지 않기 때문에 생략하겠다. 단순히 주석으로 처리한 순번을 보면, 대게 맨 상단에 생성자들을 다 생성하고 코드의 흐름에 따라서 값들을 주입해주는 형식으로 진행된다.

 

이렇게 변수의 위치를 지정하였을 때, 제삼자는 메서드 내부에서 흐름을 따라감과 동시에 어떤 변수에 담기는지 확인하기 위해 시선이 위로 향할 수밖에 없게 된다. 

 

각 변수의 범위를 최대한 작게 줄이고, 위치를 옮겨서 변수가 나타나는 줄의 수를 최소화해야 한다. 눈에 보이지 않으면 마음에서도 멀어지는 법이다.

 

public Map<String, Object> selectItemList(int pageNum, int pageRowCount, String searchType) {
	Map<String, Object> param = new HashMap<>(); 
        param.put("pageRowCount", pageRowCount); 
        param.put("pageNum", pageNum); 
        param.put("searchType", searchType); 

        List<ItemEntity> entityList = dao.selectItemList(param);
        List<HomePageHistoryStatisticsVO> voList = new ArrayList<>();
         
        for(ItemEntity entity : entityList){
            voList.add(entity.itemVO());
        }
        
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("data", voList);
        PagingInfoVO pagingInfo = new PagingInfoVO(entityList, pageRowCount);
        resultMap.put("pagingInfo", pagingInfo);
        return resultMap;
    }

 

위의 코드 변경처럼 각각의 정의를 실제로 사용하기 바로 직전 위치로 옮기는 것이 가독성면에서 좋다. 이 밖에도, 값이 한 번만 할당되는 변수를 선호하는 것이 좋다. Java에서는 final을 사용한 변수가 코드의 안정성 면에서도, 가독성 면에서도 훨씬 좋은 효과를 발휘한다.

 

 

코드 재작성하기

책의 3부에서는 코드 재작성에 대해 다룬다. 코드의 커다란 블록을 높은 수준에서 재조직하고 주어진 문제를 함수 수준에서 해결하는 방법을 담았다.

 

코드 분량 줄이기✂️

첫 번째로, 일반적인 목적의 코드를 프로젝트의 특정 코드에서 분리하는 것이다. 사실 생각보다 많은 코드가 일반적이다. 일반적인 문제를 해결하기 위한 라이브러리와 헬퍼 함수들로 이루어진 집합을 구성하면, 남아있는 코드는 프로그램의 작은 핵심에 불과하다. 

 

Java에서는 흔히 Utils 디렉토리안에 자주 사용하는 메서드들을 넣고, Vue.js 에서는 mixin을 사용하기도 한다. 한 가지 예로, 회원가입 진행 시에 핸드폰 번호와 이메일에 관한 정규식 표현들은 대부분 특정 코드로 분리하여 사용한다.

 

 

두 번째로, 사용하지 않는 코드를 제거하는 것이다. 대게 개발자들은 코드가 일단 작성되면 이를 없애기 꺼려한다. 현실적으로 코드 양이 업무 양을 대변하기 때문이고, 한편으로는 잘못된 삭제로 인한 오류를 만나기 꺼려하는 이유도 함께 존재한다. 

 

하지만, 이러한 이유로 사용하지 않는 코드를 방치한다면, 쓸모없는 코드들이 프로젝트 전반에 걸쳐서 흩어져 있어 추후에는 눈에 잘 띄지 않게 된다. 시간이 흐를수록 프로젝트는 무거워지게 되고, 인수인계받은 다음 사람은 쓰지 않는 코드를 하염없이 보면서 '이 코드가 무슨 용도지?'라는 미궁에 빠질 위험도 있다.

 

 

세 번째로, 자기 주변에 있는 라이브러리에 친숙해져야 한다. 개발을 할 때 고민하던 기술들이 이미 자기 주변의 라이브러리에 존재하는 경우가 허다하다. 다만, 몰랐을 뿐이다. 

 

저자는 매일 15분씩 자신의 표준 라이브러리에 있는 모든 함수/모듈/형들의 이름을 읽으라고 권장한다. 라이브러리 전체를 암기하는 것이 아닌, 그냥 그 안에 무엇이 있었는지 감을 잡아 놓고 추후에 새로운 코드를 작성할 때 "아 이거 저번에 봤던 것 같은데"라는 생각과 함께 그 기술을 찾아 사용할 수 있어야 한다고 말한다.

 

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

마치며👋

책 한 권을 읽고 내용을 정리하고 스터디를 준비하면서 생각보다 많은 시간과 노력이 들어간다는 것을 깨달았다. 하지만 그만큼 내가 배울 수 있는 부분들이 상당히 많았던 것 같다.

 

또한, 실무에서 단순히 '기능만 동작'하게 코드를 구성해왔기 때문에, 지난날의 내가 짠 코드를 반성하게 되고, 한편으로는 앞으로 코드 한 줄 아니, 변수명 한 개라도 조금 더 넓은 관점에서 바라보면서 짜야겠다는 다짐도 함께 들었다. 

 

 

책 리뷰 포스팅

* [ 스프링 부트와  AWS로 혼자 구현하는 웹 서비스 - 이동욱(향로) ]

반응형