가독성 높은 코드를 작성하라

김민규·2023년 5월 2일
0

도서 리뷰

목록 보기
5/5
post-thumbnail

가독성의 핵심은 개발자가 코드의 기능을 빠르고 정확하게 이해할 수 있도록 하는 것이다.

서술형 명칭 사용

서술적이지 않은 이름은 코드를 읽기 어렵게 만든다

class T {
    Set<String> pns = new Set();
    Int s = 0;
    Boolean f(String n) {
        return pns.contains(n);
    }
    ...
}

숨막힌다...

주석문으로 서술적인 이름을 대체할 수 없다.

/** 팀을 나나낸다. */
class T {
    Set<String> pns = new Set(); // 팀에 속한 선수의 이름
    Int s = 0; // 팀의 점수
    
    /**
     * @param n 플레이어의 이름
     * @return true 플레이어가 팀에 속해 있는 경우
    */
    Boolean f(String n) {
        return pns.contains(n);
    }
    ...
}
  • 코드가 훨씬 더 복잡해 보인다.
  • 코드뿐만 아니라 주석문과 문서도 유지보수해야 한다.

해결책: 서술적인 이름 짓기

class Team {
    Set<String> playerNames = new Set();
    Int score = 0;
    
    Boolean containsPlayer(String playerName) {
        return playerNames.contains(playerName);
    }
    ...
}
  • 변수, 함수 및 클래스가 별도로 설명할 필요가 없이 자명하다.
  • 주석문을 사용한 경우보다 덜 지저분하고 개발자가 주석문까지 관리할 필요 없이 코드에만 집중할 수 있다.

주석문의 적절한 사용

  • 코드가 무엇을 하는지 설명 ⭐
  • 코드가 그 일을 하는지 설명 ⭐
  • 사용 지침 등 기타 정보 제공

중복된 주석문은 유해할 수 있다

String generateId(String firstName, String lastName) {
	// "{이름}.{성}"의 형태로 ID를 생성한다.
    return firstName + "." + lastName;
}

주석문을 사용해서 코드가 하는 일을 설명하지만 코드 자체로 설명이 되기 때문에 주석문은 쓸모가 없다.

주석문으로 가독성 높은 코드를 대체할 수 없다.

String generateId(String[] data) {
	// data[0]는 유저의 이름이고 data[1]은 성이다.
	// "{이름}.{성}"의 형태로 ID를 생성한다.
    return data[0] + "." + data[1];
}

🍠🍠🍠

String generateId(String[] data) {
    return firstName(data) + "." + lastName(data);
}

String firstName(String[] data) {
	return data[0];
}

String lastName(String[] data) {
	return data[1];
}

주석문은 코드의 이유를 설명하는 데 유용하다

특정 코드가 존재하는 이유나 어떤 일을 수행하는 목적은 코드를 파악하고자 하는 다른 개발자가 알 수 없는 배경 상황이나 지식과 관련 있을 수 있다.
이러한 배경 상황이나 지식이 코드를 이해하거나 안전하게 수정하기 위해 중요한 경우 주석문은 매우 유용하다.

  • 제품 또는 비즈니스 의사 결정
  • 이상하고 명확하지 않은 버그에 대한 해결책
  • 의존하는 코드의 예상을 벗어나는 동작에 대처
class User {
	private final Int username;
    private final String firstName;
    private final String lastName;
    private final Version signupVersion;
    ...
    
    String getUserId() {
    	if (signupVersion.isOlderThan("2.0")) {
        	// (v2.0 이전에 등록한) 레거시 유저는 이름으로 ID가 부여된다.
            // 자세한 내용은 #4218 이슈를 보라.
            return firstName.toLowerCase() + ".". + lastName.toLowerCase();
        }
        // (v2.0 이후로 등록한) 새 유저는 username으로 ID가 부여된다.
        return username;
    }
    ...
}      
  • 어떤 코드가 왜 존재하는지 이유를 설명하는 주석문 ⭐

주석문은 유용한 상위 수준의 요약 정보를 제공할 수 있다

/**
 * 스트리밍 서비스의 유저에 대한 자세한 사항을 갖는다.
 *
 * 이 클래스는 데이터베이스 직접 연결하지 않는다. 대신 메모리에 저장된 값으로 생성된다.
 * 따라서 이 클래스가 생성된 이후에 데이터베이스에서 이뤄진 변경 사항을 반영하지 않을 수 있다.
 */
class User {
    ...
}
  • 주석과 문서화는 코드만으로 전달할 수 없는 세부 사항을 설명하거나 코드가 큰 단위에서 하는 일을 요약하는 데 유용하다.
  • 단점으로는 이런 주석문과 문서도 유지 및 보수가 필요하고 내용이 제때 업데이트되지 않으면 코드와 맞지 않게 되고 코드가 지저분해질 수 있다.

코드 줄 수를 고정하지 말라

일반적으로 코드베이스의 코드 줄 수는 적을수록 좋다.
그러나 코드 줄 수는 우리가 실제로 신경 쓰는 것들을 간접적으로 측정해줄 뿐이다.
우리가 정말로 신경 쓰는 것은 코드에 대해 다음과 같은 사항들을 확실하게 하는 것이다.

  • 이해하기 쉽다.
  • 오해하기 어렵다.
  • 실수로 작동이 안 되게 만들기가 어렵다.

간결하지만 이해하기 어려운 코드는 피하라

Boolean isIdValid(UInt16 id) {
	return countSetBits(id & 0x7FFF) % 2 == ((id & 0x8000) >> 15);
}
  • 이 코드는 패리티 비트를 검사하는데, 이는 데이터를 전송할 때 사용되는 오류 감지를 위한 값이다.
  • 여러 개발자가 이 코드가 무엇을 하는지 이해하려고 많은 시간을 낭비할 가능성이 크다.

해결책: 더 많은 줄이 필요하더라도 가독성 높은 코드를 작성하라

Boolean isIdValid(UInt16 id) {
	return extractEncodeParity(id) == calculateParity(getIdValue(id));
}

private const UInt16 PARITY_BIT_INDEX = 15;
private const UInt16 PARITY_BIT_MASK = (1 << PARIT_BIT_INDEX);
private const UInt16 VALUE_BIT_MASK = ~PARITY_BIT_MASK;

private UInt16 getIdValue(UInt16 id) {
	return id & VALUE_BIT_MASK;
}

private UInt16 extractEncodeParity(UInt16 id) {
	return (id & PARITY_BIT_MASK) >> PARITY_BIT_INDEX;
}

// 패리티 비트는 1인 비트의 수가 짝수이면 0이고 
// 홀수이면 1이다.
private UInt16 calculateParity(UInt16 value) {
	return countSetBits(value) % 2;
}

함수 호출도 가독성이 있어야 한다

많은 함수 인수
함수 호출은 인수의 개수가 늘어나면 이해하기 힘들어진다.

매개변수는 이해하기 어려울 수 있다

sendMessage("hello", 1, true);
  • 무엇을 의미하는지 알려면 함수 정의를 살펴봐야 한다.
void sendMessage(String message, Int priority, Boolean allowRetry) {
	...
}

해결책: 명명된 매개변수 사용

sendMessage(message: "hello", priority: 1, allowRetry: true);

최근에 나온 언어에서 지원되고 있다.
모든 언어가 명명된 매개변수를 지원하는 것은 아니다.
객체 구조 분해(object destructuring)를 사용하는 타입스크립트 및 다른 형태의 자바스크립트에서 흔히 볼 수 있다.

interface SendMessageParams {
  message: string,
  priorty: number,
  allowRetry: boolean,
}

해결책: 서술적 유형 사용

class MessagePriority {
	...
    MessagePriority(Int priority) { ... }
    ...
}

enum RetryPolicy {
	ALLOW_RETRY,
    DISALLOW_RETRY
}

void sendMessage(
	String message, 
	MessagePriority priority, 
    RetryPolicy allowRetry) {
	...
}

✅ 이 함수에 대한 호출은 함수 정의를 알지 못해도 이해하기 쉽다.

sendMessage("hello", new MessagePriority(1), RetryPolicy.ALLOW_RETRY);

참조

profile
Backend Engineer, Vim User

0개의 댓글

관련 채용 정보