넘치게 채우기

5장. 형식 맞추기 본문

개발/Clean Code

5장. 형식 맞추기

riveroverflow 2023. 8. 3. 16:36
728x90
반응형

 

프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야 합니다.

코드 형식을 맞추기 위해 간단한 규칙을 정하고 그 규칙을 따라야 합니다.

필요하다면 규칙을 자동으로 적용하는 도구를 활용할 수 있습니다.

 

형식을 맞추는 목적

오늘 구현한 기능이 다음 버전에서 바뀔 확률은 매우 큽니다.

오늘 구현한 코드의 가독성은 앞으로 바뀔 코드의 품질에 지대한 영향을 미칩니다.

오랜 시간이 지나 원래 코드의 흔적을 찾기 어려울 정도로 코드가 바뀌어도 맨 처음 잡아놓은 구현 스타일과 가독성은 앞으로의 유지보수에 영향을 끼칩니다.

 

적절한 행 길이를 유지하라

자바에서 파일 크기는 클래스 크기와 밀접합니다.

위 사진은 각각 JUnit, FitNesse, testNG, TAM, JDepend, Ant, Tomcat 프로젝트를 조사한 결과입니다.

FitNesse의 평균(상자 중간)값은 65줄. 전체 파일 중 대락 1/3이 40~100줄입니다.

반면 Tomcat과 Ant는 절반 이상이 200줄을 넘고, 심지어는 수천 줄이 넘어가는 파일이 있습니다.

 

이 표를 통해 알 수 있는 것은 우리는 500줄을 넘길 필요 없이 200줄 이내로 커다란 시스템을 만들 수 있다는 것 입니다.

(FitNesse는 50,000줄에 육박하는 규모의 시스템이라고 합니다.)

일반적으로 우리는 큰 파일보다 작은 파일을 이해하기 쉬워합니다.

 

신문 기사처럼 작성하라

잘 쓰인 신문기사는 최상단에 기사를 요약하는 표제와 문단이 있고, 그 아래에 세세한 그림을 보여줍니다.

소스 파일도 신문 기사처럼 작성할 수 있습니다.

이름은 간단하면서도 설명이 가능하게 짓습니다.

이름만 보고도 올바른 모듈을 살펴보고 있는지 아닌지를 판단 할 정도로 신경 써서 짓습니다.

소스 파일 첫 부분은 고차원 개념과 알고리즘을, 아래로 내려갈 수록 의도를 세세하게 묘사하고,

마지막에는 저차원 함수와 세우 내역을 쓰면 됩니다.

 

 

개념은 빈 행으로 분리하라

코드는 왼쪽에서 오른쪽으로, 위에서 아래로 읽힙니다.

빈 행은 새로운 개념을 시작한다는 시각적인 단서입니다.

너무나 간단한 규칙이지만, 매우 중요한 역할을 합니다.

 

세로 밀집도

줄바꿈이 개념을 분리한다면, 세로 밀집도는 연관성을 의미합니다.

즉, 서로 밀집된 코드는 세로로 가까이 놓아야 한다는 것입니다.

public class ReporterConfig {
	/**
	 * 리포터 리스터의 클래스 이름
	 */
	private String m_className;

	/**
	 * 리포터 리스너의 속성
	 */
	private List<Property> m_properties = new ArrayList<Property>();
	public void addProperty(Property property) {
	m_properties.add(property);
	}
}

여기서는 불필요한 주석이 개념들을 띄워드면서 혼동을 불러일으키고 있습니다. 아래가 오히려 잘 읽히는 코드입니다.

public class ReporterConfig {

	private String m_className;
	private List<Property> m_properties = new ArrayList<Property>();

	public void addProperty(Property property) {
		m_properties.add(property);
	}

}

 

수직 거리 및 위치 맞추기

함수의 연관 관계와 동작 방식을 이해하려고 이 함수에서 저 함술 오가며 소스 파일을 뺑뺑이치며 돌으신 적 있으십니까? 이는 별로 좋지 않은 경험입니다.

 

서로 밀접한 개념은 세로로 가까이 둬야 합니다.

물론 두 개념이 서로 다른 파일에 속한다면 규칙이 통하지 않습니다. 하지만 타당한 근거가 없다면 서로 밀접한 개념은 한 파일에 속해야 마땅합니다. 이게 바로 protected 변수를 피해야 하는 이유 중 하나입니다.

 

같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현합니다.

여기서 연관성이란 한 개념을 이해하는 데 다른 개념이 필요한 정도입니다.

연관성이 깊은 두 개념이 멀리 떨어져 있으면 코드를 읽는 사람은 소스 파일과 클래스를 이곳저곳 뒤지게 됩니다.

 

1.변수 선언

변수는 사용하는 위치에 최대한 가까이 선언합니다.

루프를 제어하는 변수는 루프 문 내부에서 선언합니다

for(Test each : tests)
	count += each.countTestCases();

긴 함수에서 블록 상단이나 루프 직전에 변수를 사용하는 경우도 있습니다.

 

2.인스턴스 변수

인스턴스 변수는 클래스 맨 처음에 선언합니다.

변수 간에는 세로로 거리를 두지 않습니다.

 

C++에서는 인스턴스 변수를 클래스의 마지막에 선언한다는 가위 규칙(scissors-rule)이 적용됩니다.

그러나, 자바에서는 보통 클래스 맨 처음에 인스턴스 변수를 선언합니다.

 

중요한 것은 잘 알려진 위치에 인스턴스 변수를 모은다는 것입니다.

 

3.종속 함수

한 함수가 다른 함수를 호출한다면, 두 함수는 세로로 가까이 있어야 합니다.

가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치합니다.

이러면 자연스럽게 코드가 읽힙니다.

이러면 호출되는 함수를 찾기 쉬워지며, 모듈 전체의 가독성 역시 높아집니다.

void sayHello(String name){
	shakeHands();
	print("Hello, $name");
}
void shakeHands(){
	print("(Shaking Hands)");
}

 

4.개념적 유사성

어떤 코드는 서로 끌어당깁니다.

친화도가 높은 코드는 가까이 배치합니다.

비슷한 동작을 한다거나, 종속성이 있는 코드끼리는 친화도가 있습니다.

 

명명법이 똑같고, 기본 기능이 유사하고 간단한 코드들은 서로 붙여놓을 필요가 있습니다.

 

ex : 이모지 출력 함수와 텍스트 출력 함수는 서로 가까운 위치에 놓으면 편안합니다.

 

5.세로 순서

일반적으로 함수 호출 종속성은 아래 방향으로 유지합니다.

호출되는 함수를 호출하는 함수보다 나중에 배치하면 고차원에서 저차원으로 자연스럽게 내려갑니다.

 

신문 기사와 마찬가지로 가장 중요한 개념을 먼저 표현하고, 세세한 사항은 나중에 표기합니다.

독자는 소스 파일에서 척 함수 몇 개만 읽어도 개념을 파악하기 쉬워집니다.

 

 

가로 형식 맞추기

프로그래머는 짧은 행을 선호합니다.

120자정도가 매우 적당하죠.

 

디스플레이의 크기도 커져가고 있지만, 과하게 옆을 늘어진 코드는 피곤하게 만듭니다.

 

1. 가로 공백과 밀집도

가로로는 공백을 사용해 밀접한 개념과 느슨한 개념을 표현합니다.

int linesize = lone.length();
totalChars += linesize;
lineWidthHistogram.addLine(lineSize, lineCount);

위는 할당 연산자를 강조하기 위해 앞뒤에 공백을 썼습니다. 할당문은 왼쪽 요소와 오른쪽 요소가 분명히 나뉩니다.

공백을 통해서 두 가지 요소가 확실히 나눠집니다.

반면, 함수 이름과 이어지는 괄호 사이에는 공백이 없습니다.함수와 인수는 서로 밀접하기 때문입니다.

공백을 넣으면 별개로 보입니다.

이외에도, 연산자 우선순위를 강조하기 위해 공백을 사용할 수 있습니다.

b*b - 4*a*c

곱셈은 우선순위가 높기 때문에 붙이고, 덧셈과 뺄셈은 낮기 때문에 떨어트립니다. 이로서 가독성이 개선됩니다.

2. 가로 정렬

어셈블리어 프로그래머였던 저자는 특정 구조를 강조하고자 가로 정렬을 사용했습니다.

public class FitNesseExpediter implements ResponseSender{
	private   Socket          socket;
	private   InputStream     input;
	private   OutputStream    output;
	private   Request         request;
	private   Response        response;
	private   FitNesseContext context;
	protected long            requestParsingTimeLimit
	private   long            requestProgress
	private   long            requestParsingDeadline;
}

하지만, 이 방법은 문제가 있습니다. 변수 유형은 신경 안쓰고, 변수 이름부터 읽게 됩니다.

이들은 별도로 정렬할 필요가 없습니다.(오히려, 변수가 너무 많으니 클래스를 쪼개는 게 더 깔끔할 것 입니다)

 

 

3. 들여쓰기

들여쓰기가 적용된 코드는 구조가 한 눈에 보입니다. 반면, 들여쓰기를 적용하지 않은 코드는 암호문과 다름없습니다.

public class CommentWidget extends TextWidget{
	public static final String REGEXP = "^#[^\r\n]*(?:(?:\r\n)|\n\r?"'

	public CommentWidget(ParentWidget parent, String text){super(parent, text);}
	public String render() throws Exception {return "";}
}

위처럼 짧은 코드블럭이 있는 상황에서는 들여쓰기를 무시하는 유혹이 강합니다.

 

그러나, 이럴 때에도 들여쓰기를 적용해주는 것이 좋습니다.

public class CommentWidget extends TextWidget{
	public static final String REGEXP = "^#[^\r\n]*(?:(?:\r\n)|\n\r?"'

	public CommentWidget(ParentWidget parent, String text){
		super(parent, text);
	}
	public String render() throws Exception {
		return "";
	}
}

 

4. 가짜 범위

때로는 빈 while문이나 for문을 접하는데, 빈 코드블록을 올바로 들여쓰고 괄호로 감싸놓아야 합니다.

while(dis.read(buf, 0, readBufferSize) != -1){
	// TODO : 
}

while(dis.read(buf, 0, readBufferSize) != -1)
;

위가 좋은 예시이고, 아래가 안 좋은 예시입니다.

 

마무리

코드를 짤 때, 우리는 다른 사람들이 일기 좋아야 합니다.

혼자 코딩하는 것이 아닙니다.

다른 사람들과 함께 코드를 확인합니다.

그렇기에 동료들과 괄호와 들여쓰기 규칙, 네이밍 규칙 등을 만들어두는 것이 좋습니다.

728x90
반응형

'개발 > Clean Code' 카테고리의 다른 글

7장. 오류 처리  (0) 2023.08.05
6장. 객체와 자료 구조  (0) 2023.08.04
4장. 주석  (0) 2023.08.01
3장. 함수  (0) 2023.07.30
Clean Code 2장. 의미 있는 이름  (0) 2023.07.25