일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- springboot
- 아키텍처
- k8s
- Elastic Load Balancing
- ASG
- Auto Scaling Group
- Redis
- TypeScript
- 코드프레소
- codepresso
- docker
- ci/cd
- ELB
- AWS
- Jenkins
- kubernetes
- Typesript
- REPLICATION
- NoSQL
- Today
- Total
Study Note
Clean Code -형식 맞추기- 본문
형식을 맞추는 목적
- 코드 형식은 의사소통의 일환이며 전문 개발자의 일차적인 의무이다.
- 기존 코드는 계속된 수정에 사라질지라도 개발자의 스타일과 규율은 사라지지 않는다. 다시 말해 오늘 구현한 코드는 앞으로 바뀔 가능성이 매우 높으며 코드의 가독성은 앞으로 바뀔 코드의 품질에 영향을 미친다.
적절한 행 길이를 유지
위 이미지를 통해 500줄을 넘지 않으며 평균 200줄 정도의 파일로도 큰 시스템 구축이 가능하다는 것을 알 수 있다.
신문 기사처럼 작성하라
신문 기사가 처음에 “표제 -> 첫 문단 -> 세부 내용” 순으로 세세하게 작성하는 것처럼 소스 파일 첫 부분에 고차원 개념과 알고리즘을 설명하고, 아래로 내려갈수록 세세하게 묘사하여, 마지막에는 저차원 함수와 세부 내역을 보여준다.
개념은 빈 행으로 분리하라
각 행은 수식이나 절을 나타내고, 이런 행 묶음이 하나의 완결된 생각으로 표현된다. 때문에 하나의 생각에는 빈 행을 넣어 분리해야 한다.
- 패키지 선언부
- import문
- 각 함수 사이
두 소스 코드를 비교했을 때 빈 행이 추가된 것이 가독성이 좋다.
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);
}
수직거리
서로 밀접한 개념은 세로로 가까이 있어야 한다. 같은 파일에 속해 있는 밀접한 두 개념은 세로 거리로 연관성을 표현한다. 단 두 개념이 서로 다른 파일에 속해 있다면 이런 규칙이 통하지 않는다.
변수선언
변수는 사용하는 위치에서 최대한 가까이 선언한다.
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;
}
...
for (XmlTest test : m_suite.getTests()) {
TestRunner tr = m_runnerFactory.newTestRunner(this, test);
tr.addListener(m_textReporter);
m_testRunners.add(tr);
invoker = tr.getInvoker();
for (ITestNGMethod m : tr.getBeforeSuiteMethods()) {
beforeSuiteMethods.put(m.getMethod(), m);
}
for (ITestNGMethod m : tr.getAfterSuiteMethods()) {
afterSuiteMethods.put(m.getMethod(), m);
}
}
...
첫 코드에서 InputStream이 처럼 지역 변수는 함수 맨 처음에 선언한다.
두 번째 코드에서 Test처럼 반복문을 제어하는 변수는 반복문 내부에서 선언한다.
세번째 코드에서 TestRunner처럼 블록 상단이나 반복문 직전에 변수를 선언할 때가 가끔 있다.
인스턴스 변수
중요한 건 인스턴스 변수를 모으고 변수 간 세로 거리를 두지 않는 것이다. java처럼 클래스의 맨 처음에 선언하든, C++의 가위 규칙을 적용한 클래스 맨 마지막에 선언하든 상관없다.
종속 함수
한 함수가 다른 함수를 호출하면 두 함수는 세로 거리가 가까워야 하며 가능하면 호출하는 함수가 먼저 배치한다. 이렇게 하면 프로그램이 자연스럽게 읽히고 호출되는 함수를 찾기 쉬워져 가독성이 좋아진다.
다음 코드에서 볼 수 있듯이 호출 함수가 뒤에 있어 가독성이 높다.
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);
}
명명법, 기본 기능이 유사하고 서로 호출하는 관계 등 매우 개념적 친화도가 높은 함수들 이다.
세로순서
함수 호출 종속성은 아래 방향으로 유지한다. 그러면 소스 코드 모듈이 고차원에서 저차원으로 내려간다. 신문 기사처럼 파악하기 쉬운 코드가 된다.
가로 형식 맞추기
표의 결과로 20자 ~ 60자의 행이 40%이며, 10자 미만은 30%이다. 또한 80자 이상의 행은 급격히 감소한다.
가로 공백과 밀집도
가로 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다.
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(int a, int b, int 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 Reques request;
private Response response;
private FitNesseContex context;
protected long requestParsingTimeLimit;
private long requestProgress;
private long requestParsingDeadline;
private boolean hasError;
private boolean hasError;
public FitNesseExpediter(Socket s,
FitNesseContext Context) throws Exception
{
this.context = context;
socket = s;
input = s.getInputStream();
output = s.getOutputStream();
requestParsingTimeLimit = 10000;
}
}
들여쓰기
소스 파일은 윤곽도와 계층이 비슷하다. 계층에서 각 수준은 이름을 선언하는 범위이자 선언준과 실맹문을 해석하는 범위다. 이런 범위 계층을 표현하기 위해 들여쓰기를 한다.
- 클래스 정의와 같은 파일 수준의 문장은 들여쓰지 않는다.
- 클래스 내의 메서드에서 한 수준 들여쓴다.
- 메서드 내의 코드는 메서드 선언보다 한 수준 들여쓴다.
- 블록 코드는 블록을 포함하는 코드보다 한 수준 들여쓴다.
- 짧은 if문, while문에서 들여쓰기를 무시하지 말아야 한다.
가짜 범위
빈 while문이나 for문을 꼭! 사용해야할 때는 빈 블록을 잘 들여쓰고 괄호로 감싼다.
팀 규칙
프로그래머에게 각자 선호하는 규칙이 있지만 팀에 속해 있다면 팀 규칙이 자신이 선호해야할 규칙이다. 모든 팀원이 팀 규칙을 따라야 일관적인 소프트웨어 스타일이 된다. 스타일이 일관적이고 매끄러워야 읽기 쉬운 소프트웨어 시스템이다.