본문 바로가기

Clean Code

[Clean Code] Chapter 5 형식 맞추기

슬링키의 클린! 코드!

 

이 포스팅을 읽는 프로그래머라면 누구나!

 

깔끔하고 질서정연하고 보기 좋은 코드를 원할 것이라 생각합니다.

프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야 합니다. 이를 위해 몇 가지 규칙을 정하고 이 규칙을 따라야 하죠.


형식을 맞추는 목적

 

코드 형식은 의사소통의 일환이기에 매우 중요합니다.

의사소통은 전문 개발자의 일차적인 의무이기도 하죠.

 

'돌아가는 코드'를 작성하는 것이 일차적인 의무이지 않나요? 라고 생각할 수 있지만 이번 포스팅을 보고 다른 관점에서 접근해보길 바랍니다.

돌아가는 코드, 즉, 정상 작동하는 코드를 작성하는 것도 중요하지만 이는 후에 바뀔 가능성이 높습니다.

하지만 가독성 관점에서는?

한번 정립된 구현 스타일과 정돈된 가독성은 (이후에 코드가 변경되더라도) 유지보수 용이성과 확정성 측면에서 계속 영향을 미칩니다.

 

다시 말해, 구현한 코드는 변경 및 삭제되어 사라지더라도 개발자의 스타일과 규율은 사라지지 않습니다.

 

다음은 원활한 소통을 장려하는 코드 형식입니다.


 

적절한 행 길이를 유지하라 (세로 형식 맞추기)

 

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

클래스 크기를 논외로 치고, 파일 크기만 고려한다는 가정하에 아래 그림을 확인해봅시다.

 

대표적 프로젝트 7개의 파일 길이 분포

그림을 확인해보면 프로젝트별 다양한 행 길이를 갖습니다.

위 그림에서 알 수 있는 것은 500줄이 넘지 않고도, 평균 200줄 정도로도 시스템을 구축할 수 있다는 점입니다.

긴 길이의 행으로 짜여진 프로젝트가 무조건 좋은 프로젝트라는 점을 시사하지 못한다는 의미를 내포하고 있습니다.

 

일반적으로 큰 파일보다 작은 파일이 이해하기 쉽습니다.


 

신문 기사처럼 작성하라

 

정갈한 코드를 신문에 비유해 생각해 봅시다.

신문에는 내용을 요약해주는 표제, 전체 기사 내용을 요약해주는 첫 문단, 그리고 읽어 내려갈수록 세세한 사실 및 사항들이 나오죠.

 

소스파일도 신문 기사와 비슷하게 작성한다고 생각하면 편합니다.

이름은 이름만 보고도 모듈의 역할을 알 수 있게끔 간단하면서도 설명이 가능하게 신경써서 짓습니다.

소스 파일 첫 부분은 고차원 개념과 알고리즘을 설명하고 내려갈수록 의도를 세세하게 묘사합니다.

마지막에는 가장 저차원 함수와 세부 내역들이 나오게 됩니다.

 

또한, 신문이 임팩트있고 짧게 구성된 것 처럼, 소스 코드 또한 정돈되고 길지 않게 짜도록 연습하는 편이 좋습니다.


 

개념은 빈 행으로 분리하라

 

글은 국가마다 왼쪽에서 오른쪽으로, 오른쪽에서 왼쪽으로 다르게 읽히기도 합니다.

하지만 거의 모든 코드는 왼쪽에서 오른쪽으로, 위에서 아래로 읽힙니다.

각 행은 수식이나 절을, 일련의 행 묶음은 완결된 생각 하나를 표현합니다.

따라서, 생각 사이는 빈 행을 넣어 분리하는 것이 합리적이라 할 수 있죠.

 

이처럼 빈 행은 새로운 개념을 시작한다는 시각적 단서이기에, 코드에서도 패키지 선언부, import 문, 함수 사이에 빈 행이 들어가게 됩니다. 책을 읽을 때 쉽표, 문단 구분으로 호흡을 조절하듯이 코드에서도 빈 행을 통해 코드의 가독성을 높여준다고 볼 수 있습니다.

 

더 직관적인 이해를 위해 아래 코드를 확인해 봅시다.

 

package fitnesse.wikitext.widgets;

import java.util.regex.*;

public class BoldWidget extends ParentWidget{
	public static final String REGEXP ="''',+?'''";
    private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
    	Pattern.MULTILINE + Pattern.DOTAIL
    );
    
    public BoldWidget(ParentWidget parent, String text) throws Exception{
    	super(parent);
        Mathcher match = pattern.matcher(text);
        match.find();
        addChildWidgets(match.group(1)));
    }
    
    public String render() throws Exception{
    	StringBuffer html = new StringBuffer("<b>");
        html.append(childHtl=ml()).append("<\b>");
        return html.toString();
    }
}

 

package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget{
	public static final String REGEXP ="''',+?'''";
    private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
    	Pattern.MULTILINE + Pattern.DOTAIL);
    public BoldWidget(ParentWidget parent, String text) throws Exception{
    	super(parent);
        Mathcher match = pattern.matcher(text);
        match.find();
        addChildWidgets(match.group(1)));}
    public String render() throws Exception{
    	StringBuffer html = new StringBuffer("<b>");
        html.append(childHtl=ml()).append("<\b>");
        return html.toString();
    }
}

 

 

단순히 행의 존재 유무일 뿐인데, 위의 코드가 아래의 코드보다 행 묶음이 분리되어 보이고 훨씬 역할이 구분되어 보입니다.

 

빈 행을 사용함으로써 코드 가독성에 있어 긍정적인 효과를 준다는 것을 알 수 있습니다.

 


 

세로 밀집도

 

줄바꿈이 개념을 분리한다면 세로 밀집도는 연관성을 의미합니다. 세로 밀집도는 쉽게 말해 위아래로의 가까운 정도라고 생각하면 됩니다.

이번 개념은 직관적으로도 받아들일 수 있으실 겁니다.

 

예를 들어 다음과 같이 인스턴스 변수를 의미없이 떨어뜨려 놓는 경우, 변수와 함수의 관계성이 떨어져 코드를 이해하기 위해서는 읽는 사람의 추가적인 노력이 필요할 수도 있습니다.

public class ReporterConfig{
	/**
    *리포터 리스터의 클래스 이름
    */
    private String m_className;
    
    /**
    *리포터 리스너의 속성
    */
    private List<Property> m_properties = new ArrayList<Property>();
    public void addProperty(Property property){
    	m_properties.add(property);
    }
}

/**
* VS
*/

public class ReporterConfig{
	private String m_className;
    private List<Property> m_properties = new ArrayList<Property>();
    
    public void addProperty(Property property){
    	m_properties.add(property);
    }
}

 

확실히 아래의 코드가 2개의 변수와 1개의 메소드로 이루어져 있다는 것을 한눈에 확인할 수 있으실 겁니다.

이러한 점에서 세로 밀집도가 코드 가독성에 영향을 준다는 것을 알 수 있습니다.

 


 

수직 거리

 

함수의 연관 관계나 상속 관계를 찾기 위해 위아래 이리저리 왔다갔다 한 경험은 누구나 한번씩 있을 겁니다.

시스템이 무엇을 하는지 이해하고 싶은데, 이 조각 저 조각이 어디에 있는지 찾고 기억하느라 시간과 노력을 소모하기도 하죠.

 

따라서 찾는 수고를 덜기 위해 한 파일 내 서로 밀접한 개념은 세로로 가까이 둬야 합니다.

서로 다른 파일에 존재할 수는 있겠지만 타당한 근거가 없다면 서로 밀접한 개념은 한 파일에 속해야 하는 것이 마땅하고 이게 바로 protected 변수를 피해야 하는 이유 중 하나인 것이죠.

 

접근 제한자

같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현합니다. 여기서 연관성이란 한 개념을 이해하는데 다른 개념이 중요한 정도를 말합니다. 연관성 있는 두 개념을 수직 거리가 멀게 배치하게 되면 결국 코드를 읽는 사람은 이리 저리 찾아다녀야 하는 수고를 해야 합니다.

 

이제 다음 수직 거리 연관성 요소에 대해서 알아보도록 합시다.

  • 변수 선언
  • 인스턴스 변수
  • 종속 함수
  • 개념적 유사성

변수 선언: 변수는 사용하는 위치에 최대한 가깝게 선언합니다. 이전 챕터(함수)에 따르면 함수는 최대한 단순한 역할만 수행하는 최소한의 단위이기에 매우 짧습니다. 따라서 지역 변수는 각 함수 맨 처음에 선언합니다.

지역 변수는 함수 맨 앞에!

 

인스턴스 변수: 일반적인 변수와는 다르게 인스턴스 변수는 클래스의 맨 처음에 선언합니다. 또한 변수간에 세로로 거리를 두지 않습니다.

잘 설계한 클래스는 클래스 메소드가 인스턴스 변수를 사용하기 때문이죠.

(언어에 따라 인스턴스 변수 선언 위치를 클래스 최상단으로 할지 최하단으로 할지 의견이 분분하다지만 모여만 있다면 크게 상관은 없다고 봅니다)

 

 

종속 함수: 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치합니다. 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치합니다. 그러면 코드를 위에서 아래로 읽는 자연스러운 흐름에 부합되어 전체적으로 자연스럽게 읽힙니다.

이러한 방식의 장점은, 이러한 규칙을 처음부터 일관되게 사용한다면 코드를 읽는 제3자들은 방금 호출한 함수가 잠시 뒤에 정의되리라는 사실을 짐작할 수 있다는 점입니다.

 

 

개념적 유사성: 개념적인 친화도가 높은 코드들은 서로 끌어당깁니다. 따라서 친화도가 높은수록 코드를 가까이 배치하는 편이 가독성 측면에서 좋습니다.

그렇다면 친화도가 높은 요인에는 무엇이 있을까 라는 의문이 듭니다. 한 함수가 다른 함수를 호출하는 경우, 한 변수와 그 변수를 사용하는 함수 등이 좋은 예입니다. 다음은 Junit 4.3.1에서 가져온 코드입니다.

 

Junit 4.3.1

위 함수들은 명명법이 똑같고 기본 기능이 유사하고도 간단하다는 점에서 개념적인 친화도가 매우 높습니다. 서로가 서로를 호출하는 관계는 부차적인(secondary) 요인이며 종속적인 관계가 없더라도 가까이 배치해야할 함수들입니다.


 

 

세로 순서

 

이번 개념은 위 개념들의 종합적인 개념이라고 볼 수 있습니다.

일반적으로 함수 호출 종속성은 아래 방향으로 유지합니다. 위 개념 '종속 함수'에서 언급했듯이 호출되는 함수는 호출하는 함수 바로 아래에 정의하게 되면 코드를 읽어가는 흐름이 자연스럽게 위에서 아래로 유지되죠. 즉, 다시말해 소스코드 모듈이 고차원에서 저차원으로 자연스럽게 내려갑니다.

 


지금까지 수직적인 관점에서의 형식에 대해서 알아보았다면 이제는 수평적인 관점에서의 형식에 대해서 알아볼 차례입니다.


가로 형식 맞추기

한 행의 가로 길이는 얼마나 길어야 적당할까라는 의문에는 요약하자면 짧은 행이 바람직하다고 할 수 있습니다.

홀러리스(Hollerith)가 제시한 80자 제한은 다소 인위적이라는 생각이 들며, 100자 120자도 괜찮다고 볼 수 있습니다.

즉, 정해짐 가로 길이는 없다는 점입니다.

화면의 크기가 커지고 프로그래머의 나이와 취향에 따라, 가로 행에 놓이게 되는 코드 글자 수는 달라지기에 명확한 기준을 정할 수는 없지만 이 책의 저자는 120자 정도를 적당하다고 주장합니다.


 

가로 공백과 밀집도

 

가로로는 공백을 사용해 밀접한 개념과 느슨한 개념을 표현합니다. 구분하고자 하는 요소와 밀접한 관계를 표현하고 싶은 요소를 공백(_)으로써 분명히 한다는 것이죠. 다음 함수 확인해보죠.

밀접한 개념과 느슨한 개념

위 두 함수의 두 개의 표시를 확인해보면

할당 연산자를 강조하기 위에 앞 뒤에 공백을 주었고, 할당문의 왼쪽 요소와 오른쪽 요소가 분명히 나뉘는 것을 확인할 수 있습니다.

반면, 함수 이름과 이어지는 괄호 사이에는 공백을 넣지 않는 것으로써 함수와 인자가 밀접한 개념을 가진다는 것을 표현합니다. 또한 쉽표 및 공백으로 두 인수를 구분하여 두 인수가 별개라는 사실을 보여줍니다.

 

위 함수와 별개로 연산자 우선순위를 강조하기 위해 연산자 우선순위에 따라 공백을 부여하기도 합니다.

하지만 이러한 방식은, IDE에 따라 공백을 없애버려 가끔식은 무시되는 경우가 발생하기도 합니다.


가로 정렬

 

특정 구조를 강조하고자 가로 정렬을 사용하는 경우도 있습니다.

형식에서 정돈된 느낌을 줄 수는 있으나 엉뚱한 부분을 강조해 진짜 의도를 가릴 수도 있죠. 아니 의도 해석을 방해한다고 볼 수 있습니다. 

 

public FitNesseExpediter(Socket FitNesseContext context) throws Exception{
	this.context = 				context;
    socket = 					s;
    input = 					s.getInputStream();
    output = 					s.getOutputStream();
    requestParsingTimeLimit = 	100000;
}

위와 같은 코드를 확인해보면 할당 연산자에 시선이 간다기 보다는 피연산자에 더 이목이 집중되는 것을 확인할 수 있습니다. 이는 집중해야할 부분에 집중하지 못하고 다른 부분이 부각되는 유용하지 못한 결과를 이끌어냅니다.

(위와 같이 코드를 짜도 특정 도구는 위와 같은 정렬을 무시할 수도 있습니다)

 

선언문과 할당문을 별도로 정렬하지 않아야 합니다. 정렬이 필요할 정도로 목록이 길다면 그것은 목록 길이의 문제이지 정렬의 문제가 아닙니다. 이를 확실하게 구분하는 것이 중요하죠.


들여쓰기

 

처음 코드를 접할 때부터 저희가 배우게 되는 것이 바로 들여쓰기이죠? 

소스파일은 계층이 있습니다. 여기서 말하는 계층은 파일, 클래스, 메소드, 블록 등이라고 볼 수 있는데, 계층에서 각 수준은 이름을 선언하는 범위이자 선언문과 실행문을 해석하는 범위입니다.

이러한 범위로 이루어진 계층을 표현하기 위해 코드를 들여쓰는 것이라 저자는 언급합니다.

 

클래스 내 메소드를 하나 들여쓰고, 그 메소드 안의 메소드 블록을 하나 또 들여쓰고, 이런식으로 코드가 속하는 범위를 시각적으로 표현하여 코드의 가독성을 높인다고 보시면 이해하기 편하실겁니다.

 

들여쓰기 하지 않은 코드
들여쓰기 한 코드

같은 문법과 동작인 코드임에도 들여쓰기의 유무 하나로 코드의 가독성이 달라졌습니다. 이처럼 범위 수준을 들여쓰기를 통하여 시각적으로 나타내는 것이 전반적으로 좋습니다.

 

들여쓰기 무시하기?

조건문이나 반복문의 구현부가 한줄로 끝나는 경우 들여쓰기를 하지 않고 한 줄로 작성하는 경우가 많은데(저도 그렇게 하고요) 저자는 이또한 들여쓰기로 범위를 뭉뚱그리지 않고 수준 정도를 잘 나타내라고 권장합니다.

 

가짜 범위?

빈 while문이나 for문을 사용하는 경우도 있고 한 줄로 끝나는 반복문이 있을 수도 있는데 아래의 코드와 같이 새로운 행에 세미클론(;)을 붙여 코드를 읽는 사람이 충분히 인지할 수 있도록 표시를 해야 합니다.

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

정리

좋은 소프트웨어 시스템은 읽기 쉬운 문서로 이루어진다는 사실이 중요합니다.

우리는 흔히 또 자주 코드 동작에 안주하고 이 점이 가장 중요한 요소라고 생각하며 코딩을 합니다.

 

하지만 이번 포스팅을 보고 확인할 수 있듯이 코드는 언제든지 변동될 가능성이 농후하고 언제든지 확장될 가능성이 있기에 이제부터 우리는 코드의 단순 구현보다는 가독성 측면을 생각해야 합니다.

 

위 글의 장황한 내용들을 단순하게 요약하자면, 세로와 가로의 길이의 정도, 들여쓰기 등을 고려하여 처음부터 끝까지 일관된 스타일과 형식으로 코드를 구성하는 것이 매우 중요합니다. 그래야 코드를 작성한 본인, 같이 코드를 작성할 팀원, 유지 보수하며 코드를 읽을 제3자 모두에게 좋은 영향을 줄 것입니다.

 

충분히 납득할만한 내용이므로 우리 모두 혼돈의 편리함 보다는 정돈된 수고를 통한 코드를 작성하도록 노력합시다!

'Clean Code' 카테고리의 다른 글

Chapter7 오류 처리  (0) 2022.02.09
[Clean Code] Chapter 6 객체와 자료구조  (0) 2022.02.02
[Clean Code] Chapter 4 주석  (0) 2022.01.20
[Clean Code] Chapter 3 함수  (0) 2022.01.11
[Clean Code] Chapter 2 의미있는 이름  (1) 2022.01.06