IT 공부/Clean Code

Clean Code 요약해보기 (4)

수박한암살자 2022. 5. 1. 19:21

오랜만에 클린코드 다시 읽는다.

그동안 공부를 안한 건 아니지만 습관이 역시 끊기면 이렇게 된다 ㅠㅠ

 

5장부터 다시 스타트.

 


5. 형식 맞추기

형식을 맞추는 목적

- 코드 형식은 의사소통의 일환이다. 의사소통은 전문 개발자의 일차적인 의무다.

 

적절한 행 길이를 유지하라

- JUnit, FitNess, Time and Money, testNG, JDepend, Ant, Tomcat 프로젝트들을 서로 비교해보면 코드 줄이 적어도 커다란 시스템을 구축할 수 있다는 사실을 알 수 있다. 반드시 그럴 필요는 없지만 줄여보도록 노력하자.

 

신문 기사처럼 작성하라

- 이름은 간단하면서도 설명이 가능하게 짓는다. 이름만 보고도 올바른 모듈을 살펴보고 있는지 아닌지를 판단할 정도로 신경 써서 짓는다. 소스 파일 첫 부분은 고차원 개념과 알고리즘을 설명한다. 아래로 내려갈수록 의도를 세세하게 묘사한다. 마지막에는 가장 저차원 함수와 세부 내역이 나온다.

 

개념은 빈 행으로 분리하라

- 예제 코드와 함께 보자. 행이 없으면 코드 가독성이 현저하게 떨어진다.

 

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.DOTALL
    );
    
    public BoldWidget(ParentWidget parent, String text) throws Exception {
        super(parent);
        Matcher match = pattern.matcher(text);
        match.find();
        addChildWidgets(match.group(1));
    }
    
    public String render() throws Exception {
        StringBuffer html = new StringBuffer("<b>");
        html.append(childHtml()).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.DOTALL
    );
    public BoldWidget(ParentWidget parent, String text) throws Exception {
        super(parent);
        Matcher match = pattern.matcher(text);
        match.find();
        addChildWidgets(match.group(1));
    }   
    public String render() throws Exception {
        StringBuffer html = new StringBuffer("<b>");
        html.append(childHtml()).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);
    }
}


// 세로를 가깝게 해주자

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 Assert {
    static public void assertTrue(String message, boolean condition) {
        if (!condition)
            fail(message);
    }
    
    static public void assertTrue(boolean condition) {
        assertTrue(null, condition);
    }
    
    static public void assertFalse(String message, boolean condition) {
        assertTrue(message, !condition);
    }
    
    static public void assertFalse(boolean condition) {
        assertFalse(null, condition);
    }
...생략

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

 

가로 형식 맞추기

- 가로도 짧은 행이 바람직하다. 100~120자 정도면 적당하다고 책에서는 말하고 있다. (비록 모니터가 크더라도...)

 

가로 공백과 밀집도

- 가로로는 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다. 아래 함수를 보자.

private void measureLine(String line) {
    lineCount++;
    int lineSize = line.length();
    totalChars += lineSize;
    lineWidthHistogram.addLine(lineSize, lineCount);
    recordWidestLine(lineSize):
}

할당 연산자를 강조하려고 앞뒤에 공백을 줬다. 할당문은 왼쪽 요소와 오른쪽 요소가 분명히 나뉜다. 공백을 넣으면 두 가지 요소가 확실히 나뉜다는 사실이 더욱 분명해진다.

 

반면, 함수 이름과 이어지는 괄호 사이에는 공백을 넣지 않았다. 함수와 인수는 서로 밀접하기 때문이다.

 

연산자 우선순위를 강조하기 위해서도 공백을 사용한다. 

 

가로 정렬

- 코드를 먼저 보도록 하자.

public class FitNessExpediter implements ResponseSender
{
    private   Socket               socket;
    private   InputStream          input;
    private   OutputStream         output;
    private   Request              request;
    ...
}

이런 코드는 유용하지 못하다. 코드가 엉뚱한 부분을 강조해 진짜 의도가 가려지기 때문이다. 예를 들어, 위 선언부를 읽다 보면 변수 유형은 무시하고 변수 이름부터 읽게 된다.

 

들여쓰기 무시하기

- 때로는 들여쓰기를 안하고 싶기도 하다. 하지만 한 행에 다 작성하기 보다는 펼치는 것이 났다.

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 "";
    }
}

 

가짜 범위

- 때로는 빈 while 문이나 for 문을 접한다. 이런 경우 빈 블록을 올바로 들여쓰고 괄호로 감싼다. 그리고 세미콜론은 세 행에다 제대로 들여써서 넣어준다.

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

 

팀 규칙

- 팀은 한 가지 규칙에 합의해야 한다. 그리고 모든 팀원은 그 규칙을 따라야 한다. 

반응형