Skip to content

Commit 22ee4ea

Browse files
authored
Merge pull request #1 from jhkman/main
2 parents d0644e5 + cc6a7cd commit 22ee4ea

File tree

1 file changed

+368
-0
lines changed

1 file changed

+368
-0
lines changed

5. 형식 맞추기/Readme.md

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
2+
# 형식 맞추기
3+
프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야 한다. 코드 형식을 맞추기 위한 간단한 규칙을 정하고 그 규칙을 착실히 따라야 한다.
4+
5+
## 1. 형식을 맞추는 목적
6+
7+
코드 형식은 의사소통의 일환이다. 맨처음 잡아놓은 구현 스타일과 가독성 수준은 유지보수 용이성과 확장성에 계속 영향을 미친다.
8+
9+
10+
## 2. 적절한 행 길이를 유지하라
11+
12+
적은 줄의 파일로도 커다란 시스템을 구축할수 있다. 반드시 지킬 엄격한 규칙은 아니지만 바람직한 규칙으로 삼아라.
13+
일반적으로 큰 파일보다 작은 파일이 이해하기 쉽다.
14+
15+
### 2-1. 신문 기사처럼 작성하라
16+
17+
이름은 간단하면서도 설명이 가능하게 짓는다. 이름만 보고도 올바른 모듈을 살펴보고 있는지 아닌지를 판단할 정도로 신경써서 짓고,
18+
소스파일 첫 부분은 고차원 개념과 알고리즘을 설명한다. 아래로 내려갈수록 의도를 세세하게 묘사한다.
19+
마지막에는 가장 저차원 함수와 세부내역이 나온다.
20+
21+
### 2-2. 개념은 빈 행으로 분리하라
22+
23+
빈 행은 새로운 개념을 시작한다는 시각적 단서다. 코드를 읽어 내려가다 보면 빈 행 바로 다음 줄에 눈길이 멈춘다.
24+
```
25+
//빈 행으로 분리시 가독성이 좋다
26+
package fitnesse.wikitext.widgets;
27+
28+
import java.util.regex.*;
29+
30+
public class BoldWidget extends ParentWidget{
31+
public static final String REGEXP = "'''.+?'''"
32+
33+
pravate static final Pattern pattern = Pattern.compile("'''(.+?)'''", Pattern.MULTILINE + Pattern.DOTALL);
34+
35+
public BoldWidget(ParentWidget parent, String text) throws Exception {
36+
super(parent);
37+
Matcher match = pattern.matcher(text);
38+
match.find();
39+
addChildWidgets(match.group(1));
40+
}
41+
42+
public String render() throws Exception {
43+
StringBuffer html = new StringBuffer("<b>");
44+
html.append(childHtml()).append("</b>");
45+
return html.toString();
46+
}
47+
}
48+
49+
50+
// 빈 행이 없는 경우, 가독성이 현저히 떨어진다
51+
package fitnesse.wikitext.widgets;
52+
import java.util.regex.Matcher;
53+
import java.util.regex.Pattern;
54+
public class BoldWidget extends ParentWidget {
55+
public static final String REGEXP = "'''.+?'''";
56+
private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
57+
Pattern.MULTILINE + Pattern.DOTALL
58+
);
59+
public BoldWidget(ParentWidget parent, String text) throws Exception {
60+
super(parent);
61+
Matcher match = pattern.matcher(text);
62+
match.find();
63+
addChildWidgets(match.group(1));
64+
}
65+
public String render() throws Exception {
66+
StringBuffer html = new StringBuffer("<b>");
67+
html.append(childHtml()).append("</b>");
68+
return html.toString();
69+
}
70+
}
71+
72+
```
73+
74+
75+
### 2-3. 세로 밀집도
76+
77+
세로 밀집도는 연관성을 의미한다. 서로 밀접한 코드행은 세로로 가까이 놓여야 한다.
78+
```
79+
//주석으로 두 인스턴스 변수를 떨어뜨려 놓음
80+
public class ReporterConfig {
81+
/**
82+
* 리포터 리스너의 클래스 이름
83+
*/
84+
private String m_className; //인스턴스 변수 1
85+
86+
/**
87+
* 리포터 리스너의 속성
88+
*/
89+
private List<Property> m_properties = new ArrayList<Property>(); //인스턴스 변수 2
90+
public void addProperty(Property property) {
91+
m_properties.add(property);
92+
}
93+
94+
//두 인스턴스 변수를 밀접하게 배치
95+
public class ReporterConfig {
96+
private String m_className; //인스턴스 변수 1
97+
private List<Property> m_properties = new ArrayList<Property>(); //인스턴스 변수 2
98+
99+
public void addProperty(Property property) {
100+
m_properties.add(property);
101+
}
102+
```
103+
104+
105+
106+
### 2-4. 수직 거리
107+
108+
같은 파일에 속할 정도로 밀접한 개념은 세로거리로 연관성을 표현한다. 여기서 연관성이란 한 개념을 이해하는데 다른 개념이 중요한 정도다.
109+
연관성이 깊은 두 개념이 멀리 떨어져 있으면 코드를 읽는 사람이 소스 파일과 클래스를 여기저기 뒤지게 된다.
110+
111+
#### 2-4-1. 변수선언
112+
113+
변수는 사용하는 위치에 최대한 가까이 선언한다. 지역 변수같은 경우 각 함수 맨 처음에 선언한다.
114+
```
115+
private static void readPreferences() {
116+
InputStream is = null; //지역변수는 각 함수 맨 처음에 선언한다.
117+
118+
try {
119+
is = new FileInputStream(getPreferencesFile());
120+
setPreferences(new Properties(getPreferences()));
121+
getPreferences().load(is);
122+
} catch (IOException e) {
123+
try {
124+
if (is != null)
125+
is.close();
126+
} catch (IOException e1) {
127+
}
128+
}
129+
}
130+
```
131+
132+
133+
#### 2-4-2. 인스턴스 변수
134+
135+
자바에서는 인스턴스 변수는 클래스 맨 처음에 선언한다. 인스턴스 변수 간에 세로로 거리를 두지 않는다
136+
일반적으로 C++에서는 모든 인스턴스 변수를 클래스 마지막에 선언하는 가위 규칙(scissors rule)을 적용하지만,
137+
중요한 건 잘 알려진 위치에 인스턴스 변수를 모아놓아야 하며 변수 선언을 어디서 찾을지 모두가 알고 있어야 한다.
138+
139+
#### 2-4-3. 종속 함수
140+
한 함수가 다른 함수를 호출할 경우,
141+
두 함수는 세로로 가까이 배치하며 호출하는 함수를 호출되는 함수보다 먼저 배치한다.
142+
규칙적으로 함수를 배치하면 호출되는 함수를 찾기 쉬워지며 그만큼 모듈 전체의 가독성도 높아진다.
143+
```
144+
public class WikiPageResponder implements SecureResponder {
145+
protected WikiPage page;
146+
protected PageData pageData;
147+
protected String pageTitle;
148+
protected Request request;
149+
protected PageCrawler crawler;
150+
151+
public Response makeResponse(FitNesseContext context, Request request)
152+
throws Exception {
153+
String pageName = getPageNameOrDefault(request, "FrontPage"); //getPageNameOrdefault 함수를 첫번쨰로 호출
154+
loadPage(pageName, context); //loadPage 함수를 두번쨰로 호출
155+
if (page == null) //결과에 따라 notFoundResponse 혹은 makePageResponse 함수를 순차적으로 호출
156+
return notFoundResponse(context, request);
157+
else
158+
return makePageResponse(context);
159+
}
160+
161+
private String getPageNameOrDefault(Request request, String defaultPageName) //첫번째로 getPageNameOrdefault 함수 정의
162+
{
163+
String pageName = request.getResource();
164+
if (StringUtil.isBlank(pageName))
165+
pageName = defaultPageName;
166+
167+
return pageName;
168+
}
169+
170+
protected void loadPage(String resource, FitNesseContext context) //두번째로 loadPage 함수 정의
171+
throws Exception {
172+
WikiPagePath path = PathParser.parse(resource);
173+
crawler = context.root.getPageCrawler();
174+
crawler.setDeadEndStrategy(new VirtualEnabledPageCrawler());
175+
page = crawler.getPage(context.root, path);
176+
if (page != null)
177+
pageData = page.getData();
178+
}
179+
180+
private Response notFoundResponse(FitNesseContext context, Request request) //세번째로 notFoundResponse 함수 정의
181+
throws Exception {
182+
return new NotFoundResponder().makeResponse(context, request);
183+
}
184+
185+
private SimpleResponse makePageResponse(FitNesseContext context) //네번째로 makePageResponse 함수 정의
186+
throws Exception {
187+
pageTitle = PathParser.render(crawler.getFullPath(page));
188+
String html = makeHtml(context);
189+
190+
SimpleResponse response = new SimpleResponse();
191+
response.setMaxAge(0);
192+
response.setContent(html);
193+
return response;
194+
}
195+
}
196+
```
197+
198+
#### 2-4-4. 개념적 유사성
199+
200+
개념적인 친화도가 높을수록 코드를 가까이 배치한다. 친화도가 높은 요인은 여러가지인데,
201+
한 함수가 다른 함수를 호출해 생기는 직접적인 종속성이 한 예다.
202+
변수와 그 변수를 사용하는 함수도 한 예다. 비슷한 동작을 수행하는 일군의 함수또한 좋은 예이다.
203+
```
204+
public class Assert {
205+
static public void assertTrue(String message, boolean condition) {
206+
if (!condition)
207+
fail(message);
208+
}
209+
210+
static public void assertTrue(boolean condition) {
211+
assertTrue(null, condition);
212+
}
213+
static public void assertFalse(String message, boolean condition) {
214+
assertTrue(message, !condition);
215+
}
216+
static public void assertFalse(boolean condition) {
217+
assertFalse(null, condition);
218+
}
219+
}
220+
```
221+
222+
## 3. 가로 형식 맞추기
223+
224+
요즘 모니터가 커져 보여지는 길이가 길어졌지만, 프로그래머는 평균적으로 짧은 행을 선호한다.
225+
길게 작성한 코드여도 최대 120자 정도의 행 길이로 제한하는것을 지향한다.
226+
227+
### 3-1. 가로 공백과 밀집도
228+
229+
가로 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다. 함수 이름과 이어지는 괄호 사이에는 공백 넣지 않는다.
230+
함수를 호출하는 코드에서 괄호 안 인수는 공백으로 분리한다.
231+
```
232+
private void measureLine(String line) {
233+
lineCount++;
234+
int lineSize = line.length();
235+
totalChars += lineSize; //할당 연산자를 강조하기 위해 앞뒤로 공백을 준다
236+
lineWidthHistogram.addLine(lineSize, lineCount); //함수 호출하는 코드에서 괄호 안 인수는 공백으로 분리
237+
recordWidestLine(lineSize); //일반적으로 함수이름과 이어지는 괄호사이에는 공백을 주지 않는다
238+
}
239+
```
240+
241+
```
242+
//연산자 우선순위를 강조하기 위한 공백 사용 example
243+
public class Quadratic {
244+
public static double root1(double a, double b, double c) {
245+
double determinant = determinant(a, b, c);
246+
return (-b + Math.sqrt(determinant)) / (2*a); //승수사이에는 공백이 없지만 항 사이에는 공백이 들어간다.
247+
}
248+
249+
public static double root2(int a, int b, int c) {
250+
double determinant = determinant(a, b, c);
251+
return (-b - Math.sqrt(determinant)) / (2*a);
252+
}
253+
254+
private static double determinant(double a, double b, double c) {
255+
return b*b - 4*a*c;
256+
}
257+
}
258+
```
259+
260+
261+
### 3-2. 가로 정렬
262+
#### 3-2 Example1
263+
```
264+
//가로정렬을 시킨 example
265+
public class FitNesseExpediter implements ResponseSender {
266+
private Socket socket;
267+
private InputStream input;
268+
private OutputStream output;
269+
private Request request;
270+
private Response response;
271+
private FitNesseContext context;
272+
private ExecutorService executorService;
273+
private long requestParsingTimeLimit;
274+
private long requestProgress;
275+
private long requestParsingDeadline;
276+
private boolean hasError;
277+
278+
public FitNesseExpediter(Socket s,
279+
FitNesseContext context) throws IOException {
280+
this.context = context;
281+
this.socket = s;
282+
input = s.getInputStream();
283+
output = s.getOutputStream();
284+
this.requestParsingTimeLimit = 10000;
285+
}
286+
}
287+
```
288+
위 예제와 같이 정렬을 할 때 여러 단점이 있다.
289+
+ 코드가 엉뚱한 부분을 강조해 진짜 의도가 가려진다.
290+
+ 변수 유형보다 변수 이름부터 읽게 된다.
291+
+ 할당 연산자는 안 보이고 오른쪽 피연산자에 집중되게 된다
292+
+ 코드 형식을 자동으로 맞춰주는 도구는 위의 정렬을 무시한다
293+
+ 위와 같이 코드 선언부가 길면 클래스를 쪼개야 한다
294+
295+
위와 같은 이유로 아래와 같이 정렬을 하지않음으로 오히려 중대한 결함을 찾기 쉬워진다.
296+
#### 3-2 Example2
297+
```
298+
//가로정렬을 하지않은 example
299+
public class FitNesseExpediter implements ResponseSender {
300+
private Socket socket;
301+
private InputStream input;
302+
private OutputStream output;
303+
private Request request;
304+
private Response response;
305+
private FitNesseContext context;
306+
private ExecutorService executorService;
307+
private long requestParsingTimeLimit;
308+
private long requestProgress;
309+
private long requestParsingDeadline;
310+
private boolean hasError;
311+
312+
public FitNesseExpediter(Socket s, FitNesseContext context) throws IOException {
313+
this.context = context;
314+
this.socket = s;
315+
input = s.getInputStream();
316+
output = s.getOutputStream();
317+
this.requestParsingTimeLimit = 10000;
318+
}
319+
}
320+
```
321+
322+
### 3-3. 들여쓰기
323+
324+
범위로 이뤄진 계층을 표현하기 위해 코드를 들여쓴다. 들여쓰기한 코드의 경우 구조가 한눈에 들어온다.
325+
주의할점은
326+
+ 파일 수준인 문장은 들여쓰지 않는다(클래스)
327+
+ 메서드는 클래스보다 한 수준 들여쓴다
328+
+ 메서드 코드는 메서드 선언보다 한 수준 들여쓴다
329+
+ 블록 코드는 블록을 포함하는 코드보다 한 수준 들여쓴다
330+
331+
때로는 간단한 if문, 짧은 while문, 짧은 함수에서 들여쓰기를 무시하고 싶기도 하다.
332+
하지만 이런 경우에도 들여쓰기를 하는것을 지향하자.
333+
```
334+
//들여쓰기를 무시한 경우
335+
public class CommentWidget extends TextWidget {
336+
public static final String REGEXP = "^#[^\r\n]*(?:(?:\r\n)|\n|\r)?";
337+
338+
public CommentWidget(ParentWidget parent, String text) {super(parent, text);}
339+
public String render() throws Exception {return "";}
340+
}
341+
342+
//들여쓰기를 적용한 경우
343+
public class CommentWidget extends TextWidget {
344+
public static final String REGEXP = "^#[^\r\n]*(?:(?:\r\n)|\n|\r)?";
345+
346+
public CommentWidget(ParentWidget parent, String text) {
347+
super(parent, text);
348+
}
349+
350+
public String render() throws Exception {
351+
return "";
352+
}
353+
}
354+
```
355+
#### 3-3-1. 가짜범위
356+
357+
때로는 빈 while문이나 빈 for문을 접한다. body가 없는 while문은 세미콜론을 새 행에다 들여쓰자.
358+
```
359+
while(dis.read(buf, 0, readBufferSize) != -1)
360+
;
361+
//이부분은 정확히 어떤 의미인지 모르겠다...
362+
```
363+
364+
## 4. 팀 규칙
365+
366+
프로그래머라면 각자 선호하는 규칙이 있다. 하지만 팀에 속한다면 자신이 선호해야 할 규칙은 바로 팀 규칙이다.
367+
팀은 한가지 규칙에 합의하고, 모든 팀원은 그 규칙을 따르는것을 지향하자.
368+
개개인이 마음대로 짜대는 코드는 피해야한다. 좋은 시스템은 읽기 쉬운 문서로, 스타일은 일관적이고 매끄러워야 한다.

0 commit comments

Comments
 (0)