GDSC Sookmyung 활동/Speaker Session & Hands on Workshop

[Clean Code] 가독성 좋은 코드 작성 팁

림 림 2021. 5. 3. 19:45

이 글은 2021.05.03 에 진행된 코어멤버 경림님의 '가독성 좋은 코드 작성 팁' 세션을 바탕으로 작성된 블로그 포스팅입니다.

새 창에서 열기 (발표자 노트를 참고하실 수 있습니다)


가독성 좋은 코드 작성 팁

1. Clean Code란?

2. 표면적 수준에서의 코드 개선

3. 로직의 개선

참고 도서: 『클린 코드』 - 로버트 C. 마틴 저. 
                『읽기 좋은 코드가 좋은 코드다』 - 더스틴 보즈웰, 트레버 파우커 저.


1. Clean Code란?

C++의 창시자인 비야네 스트롭스트롭, UML의 창시자인 그래디 부치, OTI의 창립자인 큰 데이브 토마스는 각각 클린 코드에 대해 다음과 같이 말합니다.

“I like my code to be elegant and efficient. The logic should be straightforward and make it hard for bugs to hide, the dependencies minimal to ease maintenance, error handling complete according to an articulated strategy, and performance close to optimal so as not to tempt people to make the code messy with unprincipled optimizations. Clean code does one thing well.” - Bjarne Stroustrup

“나는 우아하고 효율적인 코드를 좋아한다. 논리가 간단해야 버그가 숨어들지 못한다. 의존성을 최대한 줄여야 유지보수가 쉬워진다. 오류는 명백한 전략에 의거해 철저히 처리한다. 성능을 최적으로 유지해야 사람들이 원칙 없는 최적화로 코드를 망치려는 유혹에 빠지지 않는다. 깨끗한 코드는 한 가지를 제대로 한다.” 

 

“Clean code is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designers’ intent but rather is full of crisp abstractions and straightforward lines of control.” - grady booch

“깨끗한 코드는 단순하고 직접적이다. 깨끗한 코드는 잘 쓴 문장처럼 읽힌다. 깨끗한 코드는 결코 설계자의 의도를 숨기지 않는다. 오히려 명쾌한 추상화의 단순한 제어문으로 가득하다.” 

 

“Clean code can be read, and enhanced by a developer other than its original author. It has unit and acceptance tests. It has meaningful names. It provides one way rather than many ways for doing one thing. It has minimal dependencies, which are explicitly defined, and provides a clear and minimal API. Code should be literate since, depending on the language, not all necessary information can be expressed clearly in code alone” - "Big" Dave Thomas

“깨끗한 코드는 작성자가 아닌 사람도 읽기 쉽고 고치기 쉽다. 단위 테스트 케이스와 인수 테스트 케이스가 존재한다. 깨끗한 코드에는 의미 있는 이름이 붙는다. 특정 목적을 달성하는 방법은 여러 가지가 아니고 하나다. 의존성은 최소이며 각 의존성은 명확히 정의한다. API는 최소로 줄였다. 언어에 따라 필요한 모든 정보를 코드만으로 명확히 표현할 수 없기에 코드는 문학적으로 표현해야 마땅하다.” 

 

이 분들의 의견을 종합해보면 클린 코드란 읽기 쉬운 코드라고 할 수 있습니다. 그러기 위해서는 다음 네 가지 사항을 지켜야 합니다.

  • 의도가 담긴 이름을 붙인다.
  • 로직을 단순하고 간단하게 한다.
  • 의존성을 최소화 한다.
  • 한가지 일만 해야 한다.

클린 코드를 지향해야 하는 이유는 버그가 없고 유지보수하기 쉬운 프로그램을 만들기 위해서입니다.


2. 표면적 수준에서의 코드 개선

코드의 흐름을 바꾸지 않고 변수, 함수, 클래스 등의 이름을 잘 붙여서 코드를 표면적 수준에서 개선할 수 있습니다.

2-1. 보편적인 이름 피하기

예제 1) tmp, temp, retval, foo 등의 의미 없는 이름 대신에 코드의 목적이나 의도를 담는 이름을 짓는 것이 좋습니다.

int tmp = 0;
for (int i = 0; i < 10; i++) {
	tmp += i * i;
}

 

 

 위의 코드에서 제곱의 합을 저장하는 변수의 이름을 tmp대신 sum_squares로 바꿉니다.

int sum_squares = 0;
for (int i = 0; i < 10; i++) {
	sum_squares += i * i;
}

 

 

예제 2) 변수의 기능이 임시로 값을 저장하는 것 뿐일 경우에만 tmp, temp를 사용하는 것이 좋습니다.

int tmp = right;
right = left;
left = tmp;

이 경우에는 tmp라는 변수가 임시로 값을 저장하는 데에만 쓰이기 때문에 tmp라는 이름이 제일 적절합니다.

 

예제 3) 인터넷에서 페이지를 가져올 경우에는 get 대신 fetch, download를 사용하는 것이 좋습니다.

def getPage(url);

getPage 메소드가 페이지를 데이터베이스에서 가져오는지, 로컬 캐시에서 가져오는지 인터넷에서 가져오는지 명확하지 않기 때문에 혼동할 수 있습니다. 인터넷에서 가져온다는 의미를 담고 싶다면 아래와 같이 바꿉니다.

def fetchPage(url);
def downloadPage(url);

 

예제 4) size라는 단어가 어떤 크기를 말하는지 혼동된다면 더 구체적인 단어를 사용하는 것이 좋습니다.

트리 자료구조

위의 사진은 트리 자료구조입니다. 아래의 코드는 트리를 나타내는 클래스를 자바스크립트로 작성한 코드입니다.

class Tree {
	int size;
	// ...
};

여기서 size는 트리의 높이, 노드의 개수, 트리가 차지하는 메모리의 크기 중에 어떤 크기를 나타내는 것인지 명확하지 않습니다. 더 구체적으로 표현하기 위해 다음과 같이 바꿔줍니다.

class Tree {
	int height;		// 트리의 높이
	int numNodes;		// 노드의 개수
	int memoryBytes;	// 메모리 크기
	// ...
};

 

2-2. 루프 반복자 이름 짓기

for (int i = 0; i < clubs.size(); i++) {
	for (int j = 0; j < clubs[i].members.size(); j++) {
		for (int k = 0; k < users.size(); k++)
			if (clubs[i].members[j] == users[k])
				cout << "user[" << k << "] is in club[" << i <, "]" << endl;
			}
		}
	}
}

 

위의 코드는 루프의 반복자로 i, j, k를 사용하고 있습니다. 이렇게 특수하게 반복해아 하는 리스트의 개수가 많을 경우에는 루프 반복자로 i, j, k  대신 해당 리스트의 반복자라는 의미를 담는 이름을 쓰는 것이 좋습니다. 
clubs의 반복자로 clubs_i 또는 ci를, members의 반복자로 members_i 또는 mi를, users의 반복자로 users_i 또는 ui를 사용하는 것이 좋습니다.

for (int ci = 0; ci < clubs.size(); ci++) {
	for (int mi = 0; mi < clubs[ci].members.size(); mi++) {
		for (int ui = 0; ui < users.size(); ui++)
			if (clubs[ci].members[mi] == users[ui])
				cout << "user[" << ui << "] is in club[" << ci <, "]" << endl;
			}
		}
	}
}

 

2-3. 불린 변수의 이름 짓기

불린 변수의 이름을 지을 때에는 그 변수가 true일 때와 false일 때의 의미가 명확해야 합니다.

bool search_name = true;

search_name이라는 변수가 true일 때의 의미가 명확하지 않습니다. 이름을 찾아야 한다는 것인지 이름을 찾았다는 것인지 알 수 없습니다. 이름을 찾았다는 의미라면 다음과 같이 수정하는 것이 좋습니다.

bool is_searched = true;

변수의 이름 앞에 is, has, can, should를 붙이면 됩니다.

 

2-4. 범위값을 나타내는 변수명 짓기

경계를 포함하는지의 여부에 따라 다른 이름을 사용합니다. 


3. 로직의 개선

로직을 단순화하면 코드의 흐름을 더 쉽게 이해할 수 있습니다.

3-1. 조건문에서 인수의 순서

if (10 <= length) // (1)
if (length >= 10) // (2)

위의 코드에서 (1)보다 (2)가 읽기 더 쉽습니다.

if (bytes_expected < bytes_received) // (1)
if (bytes_received < bytes_expected) // (2)

위의 코드처럼 인수가 모두 변수일 때는 인수의 순서를 어떻게 결정해야 할까요?

연산자의 왼쪽에는 유동적이고 비교가 되어지는 변수가, 오른쪽에는 고정적이고 비교의 대상이 되는 변수가 오는 것이 자연스럽습니다.

 

3-2. if/else 블록의 순서

if (a == b) {
	// A
}
else {
	// B
}
if (a != b) {
	// B
}
else {
	// A
}

 

위의 두 코드들은 같은 기능을 하지만  if/else 블록의 순서가 바뀌어 있습니다. 블록의 순서를 결정할 때는 다음과 같은 사항들을 고려하는 것이 좋습니다.

  • 부정이 아닌 긍정적인 블록을 앞 블록으로 보낸다.
  • 간단한 로직을 먼저 처리한다.
  • 더 흥미롭고 중요한 내용을 담는 코드를 앞 블록으로 보낸다.

 

3-3. 블록의 중첩 최소화하기

if (user_result == SUCCESS) {
	if (permission_result != SUCCESS) {
		reply.WriteErrors("error");
		reply.Done();
		return;
	}
	reply.WriteErrors("");
}
else {
	reply.WriteErrors(user_result);
}
reply.Done();

 

위의 코드에서 if문의 중첩을 줄여 아래로 개선하면 더 이해하기 쉽습니다.

if (user_result != SUCCESS) {
	reply.WriteErrors(uwer_result);
	reply.Done();
	return;
}
if (permission_resut != SUCCESS) {
	reply.WriteErrors("error");
	reply.Done();
	return;
}
reply.WriteErrors("");
reply.Done();

지금까지 클린 코드가 무엇인지와 왜 필요한지를 알아보고, 의미 있는 이름을 붙이고 로직을 단순화하면서 코드의 가독성을 높이는 몇가지 방법들을 알아봤습니다.
이 외에도 클린 코드를 지향하는 다양한 방법들이 있으니 각자 사용하는 언어에 맞게 더 공부해보시는걸 추천합니다.