## 목차 ##
## 형식을 맞추는 목적 ##
코드 형식은 의사소통의 일환이다.
- 의사소통은 전문 개발자의 의무다.
코드의 가독성은 앞으로 수정될 코드의 품질에 큰 영향을 미친다.
- 유지보수와 확장이 용이하다.
## 적절한 행 길이를 유지하라 ##
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 "";
}
}
들여쓰기가 없는 코드이다. 어떤가?
## 팀 규칙 ##
팀에 속해있다면 팀원들끼리 합의한 규칙을 따라라. 그래야 소프트웨어가 일관적인 스타일을 보인다. 개인 스타일별로 짜대는 코드는 피해야한다.