## 목차 ##



## 형식을 맞추는 목적 ##

  • 코드 형식은 의사소통의 일환이다.

    • 의사소통은 전문 개발자의 의무다.
  • 코드의 가독성은 앞으로 수정될 코드의 품질에 큰 영향을 미친다.

    • 유지보수와 확장이 용이하다.



## 적절한 행 길이를 유지하라 ##

file_length_distribution

junit, fitnesse, tam은 상대적으로 파일 크기가 작다. 대부분 200줄 미만이다. 대부분 200줄 정도인 파일들로도 큰 시스템 구축이 가능하다는걸 보여준다.

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

  • 신문 기사처럼 작성하라

    • 소스파일명은 신문의 표제처럼 이름만 보고도 올바른 모듈을 살펴보고 있는지 판단이 되도록 신경써서 짓는다.
    • 소스파일 첫 부분은 고차원 개념, 알고리즘, 함수를 작성한다.
    • 아래로 내려갈수록 의도를 세세하게 묘사한다.
    • 마지막에는 가장 저차원 함수들이 나온다.
  • 개념은 빈 행으로 분리하라

    • 빈 행은 새로운 개념을 시작한다는 시각적 단서다.
    • 일련의 행 묶음은 완결된 생각 하나를 표현한다.
//빈 행이 없는 코드
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 {
    /**
    * The class name of the reporter listener 
    */
    private String m_className;

    /**
    * The properties of the reporter listener 
    */
    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);
    }

세로 밀집도를 신경써서 수정한 코드에서는 전체적인 코드의 구조가 한눈에 쉽게 파악된다.

  • 수직 거리
    • 밀접한 두 개념은 세로 거리로 연관성을 표현한다.
    • 연관성이 깊은 두 개념이 멀리 떨어져있으면 독자가 소스 파일과 클래스를 여기저기 뒤지게 되면서 혼란스럽다.



이제부터 수직거리와 관계된 요소들을 하나씩 살펴본다.

변수 선언

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

private static void readPreferences() {
    InputStream is = null;
    try {
        is = new FileInputStream(getPreferencesFile()); 
        setPreferences(new Properties(getPreferences())); 
        getPreferences().load(is);
    } catch (IOException e) { 
        try {
            if (is != null) 
                is.close();
        } catch (IOException e1) {
        } 
    }
}
// 루프 제어 변수는 루프 문 내부에 선언한다.

public int countTestCases() { 
    int count = 0;
    for (Test each : tests)
        count += each.countTestCases(); 
    return count;
}

인스턴스 변수

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

사실 의견이 분분한데, C++의 경우는 클래스 마지막에 선언한다.

어딘지가 중요한게 아닐 모두가 예상할 만한곳에 인스턴스 변수를 모은다는 사실이 중요한 것이다.

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

** 종속 함수**

한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다.

호출하는 함수를 먼저 배치, 호출되는 함수를 뒤에 배치한다. 그러면 프로그램이 자연스럽게 읽힌다.

//makeResponse()가 호출하는 getPageNameOrDefault(), loadPage(), notFoundResponse(), makePageResponse()가 아래에 따라온다.
public class WikiPageResponder implements SecureResponder { 
    protected WikiPage page;
    protected PageData pageData;
    protected String pageTitle;
    protected Request request; 
    protected PageCrawler crawler;

    public Response makeResponse(FitNesseContext context, Request request) throws Exception {
        String pageName = getPageNameOrDefault(request, "FrontPage");
        loadPage(pageName, context); 
        if (page == null)
            return notFoundResponse(context, request); 
        else
            return makePageResponse(context); 
        }

    private String getPageNameOrDefault(Request request, String defaultPageName) {
        String pageName = request.getResource(); 
        if (StringUtil.isBlank(pageName))
            pageName = defaultPageName;

        return pageName; 
    }

    protected void loadPage(String resource, FitNesseContext context)
        throws Exception {
        WikiPagePath path = PathParser.parse(resource);
        crawler = context.root.getPageCrawler();
        crawler.setDeadEndStrategy(new VirtualEnabledPageCrawler()); 
        page = crawler.getPage(context.root, path);
        if (page != null)
            pageData = page.getData();
    }

    private Response notFoundResponse(FitNesseContext context, Request request)
        throws Exception {
        return new NotFoundResponder().makeResponse(context, request);
    }

    private SimpleResponse makePageResponse(FitNesseContext context)
        throws Exception {
        pageTitle = PathParser.render(crawler.getFullPath(page)); 
        String html = makeHtml(context);
        SimpleResponse response = new SimpleResponse(); 
        response.setMaxAge(0); 
        response.setContent(html);
        return response;
    } 
...
}

개념적 유사성

또한 비슷한 동작을 수행하는 함수군을 가까이 배치한다.

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

    ...
}

assertTrue()assertFalse()는 비슷한 동작을 수행하므로 가까이 두는것이 좋다.



## 가로 형식 맞추기 ##

  • 프로그래머는 짧은 행을 선호한다. 따라서 일반적으로 짧은 행이 바람직하다.

  • 가로 공백과 밀집도

    • 가로 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다.
    • 공백을 넣으면 별개로 보인다.
    • 밀접한 개념끼리는 공백을 사용하지 않는다.
private void measureLine(String line) { 
    lineCount++;
    int lineSize = line.length();
    totalChars += lineSize;
    lineWidthHistogram.addLine(lineSize, lineCount);
    recordWidestLine(lineSize);
}

할당 연산자는 양쪽 공백을 통해 각 요소가 확실히 나뉜다는 사실을 강조했다. 증감 연산자도 마찬가지다.

함수와 인수는 밀접하기에 함수이름과 이어지는 괄호 사이에는 공백을 넣지 않았다.

인수가 2개인 addLine() 함수에서 쉼표(,) 다음에 공백을 주어 두 인수가 별개라는 사실을 강조했다.

  • 들여쓰기
    • 파일내의 클래스, 메서드, 블록, 더 내부의 블록들로 이루어진 소스파일에서 들여쓰기는 계층을 표현해준다.
    • 들여쓰시가 없다면 코드를 읽기가 거의 불가능할 수준이 될 것이다.
    • 계층마다 한 단계의 들여쓰기를 한다.(Tab키)
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 ""; 
} 
}

들여쓰기가 없는 코드이다. 어떤가?



## 팀 규칙 ##

팀에 속해있다면 팀원들끼리 합의한 규칙을 따라라. 그래야 소프트웨어가 일관적인 스타일을 보인다. 개인 스타일별로 짜대는 코드는 피해야한다.

+ Recent posts