📕 목차
1. 형식을 맞추는 목적
2. 적절한 행 길이를 유지하라
• 신문 기사처럼 작성하라
• 개념은 빈 행으로 분리하라
• 세로 밀집도
• 수직 거리
• 세로 순서
3. 가로 형식 맞추기
• 가로 공백과 밀집도
• 가로 정렬
• 들여쓰기
• 가짜 범위
4. 팀 규칙
1. 형식을 맞추는 목적
- 코드 형식은 너무나 중요하기 때문에, 융툥성 없이 맹목적으로 따르면 안 된다.
- 코드 형식은 의사소통의 일환이며, 의사소통은 전문 개발자의 일차적 의무다.
- 오랜 시간이 지나 원래 코드의 흔적을 더 이상 찾아보기 어려울지라도, 맨 처음 잡은 구현 스타일과 가독성 수준은 유지보수 용이성과 확장성에 계속 영향을 미친다.
2. 적절한 행 길이를 유지하라
위 표를 통해, 500줄을 넘지 않고 대부분 200줄 정도인 파일로도 커다란 시스템을 구축할 수 있다는 사실을 알 수 있다.
반드시 지켜야 하는 엄격한 규칙은 아니지만 바람직한 규칙이다.
일반적으로 큰 파일보다 작은 파일이 이해하기 쉽다.
📌 신문 기사처럼 작성하라
- 소스 파일명은 간단하면서도 설명이 가능하게 지어라
- 첫 부분은 고차원 개념과 알고리즘을 설명하라
- 아래로 내려갈 수록 세세하게 묘사하라
- 마지막에는 가장 저차원 함수와 세부 내역이 나온다.
- 신문은 다양한 기사로 이루어지고, 대다수 기사가 아주 짧다. 이런 점을 유의하라.
📌 개념은 빈 행으로 분리하라
package ch05;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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<>();
public void addProperty(Property property) {
m_properties.add(property);
}
}
위 코드는 주석으로 인해 두 인스턴스 변수가 멀찍이 떨어지게 되었다.
- 줄바꿈이 개념을 분리한다면, 세로 밀집도는 연관성을 의미한다.
- 서로 밀접한 코드 행은 세로로 가까이 놓여야 한다.
📌 수직 거리
💡 같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현한다.
함수 연관 관계와 동작 방식을 이해하기 위해, 온갖 소스 파일을 뺑뺑이 돌아본 적이 있는가?
시스템을 이해하는 데 있어 조각들을 찾아다니는 것은 시간과 노력을 소모한다.
1️⃣ 변수 선언
private static void readPreferences() {
InputStream is = null;
try {
is = new FileInputStream(getPreferencesFile());
setPreferences(new Properties(getPreferences()));
} catch (IOException e) {
try {
if (is != null) is.close();
} catch (IOException ee) {
}
}
}
- 변수는 선언부와 사용부의 위치를 최대한 가까이 선언하라
- 루프를 제어하는 변수는 루프문 내부에 선언하라
- 간혹 긴 함수에서 블록 상단이나 루프 직전에 변수를 선언하는 사례도 있다.
2️⃣ 인스턴스 변수
- 클래스 맨 처음에 선언하라. (아래도 좋긴 하나, 어쨌든 변수 선언을 어디서 찾을지 모두가 알아야 한다.)
- 변수 간에 세로로 거리를 두지 않는다. 잘 설계했다면, 클래스의 대다수 메서드가 인스턴스 변수를 사용한다.
3️⃣ 종속 함수
- 한 함수가 다른 함수를 호출한다면 세로로 가깝게 배치하라
- 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치하라.
- 독자는 방금 호출한 함수가 잠시 후에 정의되리라는 사실을 예측한다.
4️⃣ 개념적 유사성
public class Assert {
public static void assertTrue(String message, boolean condition) {
if (!condition) throw new AssertionError(message);
}
public static void assertTrue(boolean condition) {
assertTrue(null, condition);
}
public static void assertFalse(String message, boolean condition) {
assertTrue(message, !condition);
}
public static void assertFalse(boolean condition) {
assertFalse(null, condition);
}
...
}
- 개념적인 친화도가 높은 코드는 서로를 끌어 당긴다.
- 한 함수가 다른 함수를 호출해 생기는 직접적 종속성
- 변수와 그 변수를 사용하는 함수
- 비슷한 동작을 수행하는 일군의 함수
- 그 외에도 여러 요인들이 있다.
- 명명법이 똑같고, 기본 기능이 유사한 경우도 가까이 배치할 함수가 된다.
📌 세로 순서
- 함수 호출 종속성은 아래 방향으로 유지하라. (호출하는 함수(고차원) → 호출되는 함수(저차원))
3. 가로 형식 맞추기
프로젝트 7개 행을 조사해본 결과, 프로그래머는 명백하게 짧은 행을 선호한다.
100자나 120자에 달해도 나쁘지 않지만, 그 이상은 주의 부족에 가깝다.
📌 가로 공백과 밀집도
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
totalChars += lineSize;
lineWidthHistogram.addLine(lineSize, lineCount);
recordWidestLine(lineSize);
}
- 할당 연산자는 공백으로 두 가지 주요 요소를 나누었다.
- 함수 이름과 이어지는 괄호 사이는 하나의 개념으로 묶기 위해 공백을 주지 않았다.
- 함수의 인자는 공백으로 분리하여 쉼표를 강조해 인수가 별개라는 사실을 보여준다.
public class Quadratic {
public static double root1(double a, double b, double c) {
double determinant = determinant(a, b, c);
return (-b + Math.sqrt(determinant)) / (2 * a);
}
public static double root2(double a, double b, double c) {
double determinant = determinant(a, b, c);
return (-b - Math.sqrt(determinant)) / (2 * a);
}
private static double determinant(double a, double b, double c) {
return b * b - 4 * a * c;
}
}
- 연산자 우선순위를 강조하기 위해 공백을 적절히 사용했다.
- 승수 사이에는 공백이 없지만, 항 사이에는 공백이 들어간다.
📌 가로 정렬
public class FitNesseExpediter implements ResponseSender {
private Socket socket;
private InputStream input;
private OutputStream output;
private Request request;
private Response response;
private FitNesseContext m_context;
protected long requestParsingTimeLimit;
private long requestProgress;
private long requestParsingDeadline;
private boolean hasError;
public FitNesseExpediter(Socket s,
FitNesseContext context) throws Exception {
socket = s;
input = socket.getInputStream();
output = socket.getOutputStream();
m_context = context;
requestParsingTimeLimit = 10000;
}
}
(꼭 한 번 씩은 해본다는 이 구조 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ)
- 유용해보이지만 실제로는 엉뚱한 부분을 강조해 진짜 의도가 가려진다.
- 변수 유형을 무시하고 변수 이름부터 읽게 된다.
- 할당 연산자는 보이지 않고 피연산자에 눈이 간다.
- 코드 형식을 자동으로 맞춰주는 도구 대다수가 위와 같은 정렬을 무시한다.
💡 정렬이 필요할 정도로 목록이 길다면 문제는 목록의 길이지 정렬 부족이 아니다!
📌 들여쓰기
- 범위(scope)로 이루어진 계층을 표현하기 위해 들여쓰기를 사용한다.
- 프로그래머는 들여쓰기 체계에 크게 의존한다.
가끔 들여쓰기 막 하는 인간들 코드 보면, 진짜 머리에 불딱밤 한 대 쥐어주고 싶다.
📌 가짜 범위
while (dis.read(buf, 0, readBufferSize) != -1)
;
- 때로 빈 while문이나 for문이 있는데, 피하지 못한다면 빈 블록을 올바로 들여쓰고 괄호로 감싸는 것이 좋다.
- 세미콜론(;)을 새 행에 들여써서 눈에 띄도록 만들어주는 것도 좋은 방법이다.
4. 팀 규칙
팀 규칙이라는 건 말 장난이고, 프로그래머는 저마다의 선호하는 규칙이 있다.
하지만 팀에 속한다면, 자신이 선호해야 할 규칙은 팀 규칙이어야 한다.
어디에 괄호를 넣고, 들여쓰기는 몇 자로 하고, 클래스와 변수와 메서드 이름을 어떻게 지을지 등을 결정한다면
설령 내가 선호하는 규칙은 아닐지라도 일관성이 생긴다.
그리고 그 일관성은 독자에게 신뢰감을 줄 수 있다.
온갖 스타일을 뒤섞어 소스 코드를 필요 이상으로 복잡하게 만드는 실수는 반드시 피하라.