형식 맞추기
들어가면서
프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야한다.
프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야한다.
코드를 본 독자들이
- 깔끔하고 일관적이며 꼼꼼하다고 감탄하면 좋겠다.
- 질서 정연하다고 탄복하면 좋겠다.
- 모듈을 읽으며 두 눈이 휘둥그래 놀라면 좋겠다.
- 전문가가 짰다는 인상을 심어주면 좋겠다.
프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야한다.
코드 형식을 맞추기 위해 규칙을 정하고 일관성 있게 지켜야 한다.
필요하다면 규칙을 적용하는 툴을 활용하는 것도 좋다.
형식을 맞추는 목적
코드 형식은 의사소통의 일환이며 전문 개발자의 일차적인 의무다.
- 오늘 구현한 코드가 다음 버전에서 바뀔 확률은 아주 높다.
- 구현 스타일과 가독성 수준은 시간이 지나도 유지보수 용이성과 확장성에 계속 영향을 미친다.
- 원래 코드는 사라질지라도 개발자 자신의 스타일과 규율은 사라지지 않는다.
코드 형식은 의사소통의 일환이며 전문 개발자의 일차적인 의무다.
'돌아가는 코드' 는 시간이 지나면 언제든지 바뀔 가능성이 있다.
'코드의 가독성' 은 시간이 지나더라도 유지보수 용이성과 확장성에 영향을 미친다.
적절한 행 길이를 유지하라
- 각 파일마다 세로의 길이는 어느 정도로 해야할까?
- 큰 파일보다 작은 파일이 일반적으로 이해하기 쉽다.
- 200줄 미만의 코드로도 충분히 크고 복잡한 시스템을 만들 수 있다.
- 반드시 지킬 엄격한 규칙은 아니지만 바람직한 규칙으로 삼자.
신문 기사처럼 작성하라
소스 파일도 신문 기사와 비슷하게 작성한다.
소스 파일도 신문 기사와 비슷하게 작성한다.
상위 단계일 수록 코드에 대해 간략히 묘사하고
하위 단계일 수록 코드에 대해 세부적인 묘사를 해야한다.
특히, 이름은 간단하면서도 설명이 가능하게 지어야한다.
이름만 보고도 올바른 모듈을 보고 있는지 아닌지를 판단할 정도로 지어야한다.
- 소스파일 첫 부분 : 고차원 개념과 알고리즘 설명
- 소스파일 중간 부분 : 내려갈 수록 의도를 세세하게 묘사
- 소스파일 마지막 부분 : 가장 저차원 함수와 세부 내역
신문은 다양한 기사로 이루어진다.
대다수의 기사가 짧으며 한 면을 채우는 기사는 거의 없다.
내용, 날짜 ,이름 등을 무작위로 뒤섞은 긴 기사 하나만 싣는다면 아무도 읽지 않으리라.
개념은 빈 행으로 분리하라
여백의 미
수정 전 코드
package SingleTone;
public class Settings {
private static class SettingsHolder {
public static final Settings settings = new Settings();
}
private Settings() { }
public static Settings getInstance() { return SettingsHolder.settings; }
private boolean darkMode = false; // default false
private int fontSize = 13; // default 13
public boolean getDarkMode(){return darkMode;}
public int getFontSize(){return fontSize;}
public void setDarkMode(boolean _darkMode){darkMode = _darkMode;}
public void setFontSize(int _fontSize){fontSize = _fontSize;}
}
수정 후 코드
package SingleTone;
public class Settings {
private static class SettingsHolder {
public static final Settings settings = new Settings();
}
private Settings() { }
public static Settings getInstance() { return SettingsHolder.settings; }
private boolean darkMode = false; // default false
private int fontSize = 13; // default 13
public boolean getDarkMode(){return darkMode;}
public int getFontSize(){return fontSize;}
public void setDarkMode(boolean _darkMode){darkMode = _darkMode;}
public void setFontSize(int _fontSize){fontSize = _fontSize;}
}
- 각 행은 수식이나 절을 나타내고, 일련의 행 묶음은 완결된 생각 하나를 표현한다.
- 그렇기에 생각 사이는 빈 행을 넣어 분리해야 마땅하다.
세로 밀집도
서로 밀접한 개념은 세로로 가까이 두어야 한다.
줄 바꿈이 개념을 분리한다면
세로 밀집도는 연관성을 의미한다.
즉, 서로 밀접한 코드 행은 세롤로 가까이 놓아야한다.
수정 전 코드
public class ReporterConfig {
/**
* 리포터 리스너의 클래스 이름
*/
private String m_className;
/**
* 리포터 리스너의 속성
*/
private List<Property> m_properties = new ArrayList<Property>();
public void addProperty(Property property) {
m_properties.add(property);
}
}
- 변수 2개, 메서드가 1개 있다.
- 하지만 각각 떨어져있으니 바로 인식하기는 쉽지 않다.
수정 후 코드
public class ReporterConfig {
private String m_className;
private List<Property> m_properties = new ArrayList<Property>();
public void addProperty(Property property) {
m_properties.add(property);
}
}
- 이전과 달리 변수2개 메서드 1개가 바로 인식된다.
이 처럼 머리나 눈을 움직일 필요 없이 코드가 읽히고
연관된 변수, 메서드가 사용되므로 가독성 또한 좋아진다.
수직거리
서로 밀접한 개념은 세로로 가까이 두어야 한다.
- 같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표시한다.
- 연관성이 깊은 두 개념이 멀리 떨어져 있으면 소스 파일과 클래스를 여기저기 뒤져야 한다.
- 즉, 연관성이 높은 데 서로 다른 파일에 존재하면 안 된다.
protected
변수를 사용 안하는 이유 -> 다른 파일 확인해야함- 연관성 : 한 개념을 이해하는데 다른 개념이 중요한 정도
변수 선언
- 변수는 사용하는 위치에 최대한 가까이 선언한다.
- 루프를 제어하는 변수는 흔히 루프 문 내부에 선언한다.
- 아주 드물지만 다소 긴 함수에서 블록 상단이나 루프 직전에 변수를 선언하는 사례도 있다.
public void method(){
InputStream is = null;
try {
is = new FileInputStream(getPreferncesFile());
...
} catch {
...
}
}
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);
...
...
}
인스턴스 변수 선언
- 클래스 맨 처음에 선언한다.
- 변수 간에 세로로 거리를 두지 않는다.
- 잘 설계한 클래스는 대다수의 메서드가 인스턴스 변수를 사용하기 때문이다.
public class SessionUser implements Serializable {
private String userId;
private String userName;
private String userNickname;
private Role userRole;
public SessionUser(User user) {
this.userId = user.getUserId();
this.userName = user.getName();
this.userNickname = user.getNickName();
this.userRole = user.getRole();
}
public boolean checkAdmin(){
return userRole.getKey().equals("ADMIN");
}
}
종속 함수
- 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다.
- 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치한다.
- 규칙을 일관적으로 적용한다면 방금 호출한 함수가 잠시 후에 정의되리라는 사실을 예측한다.
- 그러면 프로그램이 자연스럽게 읽힌다.
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 {
if (dontCreateNonExistentPage(request))
return new NotFoundResponder().makeResponse(context, request);
return new EditResponder().makeResponseForNonExistentPage(context, request);
}
private boolean dontCreateNonExistentPage(Request request) {
String dontCreate = (String) request.getInput("dontCreatePage");
return dontCreate != null && (dontCreate.length() == 0 || Boolean.parseBoolean(dontCreate));
}
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);
}
....
- 위 함수들은 명명법이 똑같고 기본 기능이 유사하고 간단하여 친화도가 매우 높다.
- 종속적인 관계가 없더라도 가까이 배치할 함수들이다.
세로 방향
- 함수 호출 종속성은 아래 방향으로 유지한다.
- 호출하는 함수 다음에 호출되는 함수를 배치한다.
- 소스 코드 모듈이 고차원에서 저차원으로 자연스럽게 내려간다.
- 소스 파일에서 첫 함수 몇 개만 읽어도 개념을 파악하기 쉬워진다.
가로 형식 맞추기
프로그래머는 명백하게 짧은 행을 선호한다.
- 행 길이는 짧은 편이 바람직하다.
- 80자까지는 아니라도 100~120자 정도는 나쁘지 않다.
- 그 이상의 길이는 주의 부족으로 생겼으며 120자 정도로 제한하는 것을 권장한다.
가로 공백과 밀집도
가로 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다.
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
lineWidthHistogram.addLine(lineSize, lineCount);
double result = (-b - Math.sqrt(determinant)) / (2*a);
}
- 할당 연산자 :
int lineSize = line.length();
- 연산자와 피연산자 사이에 공백을 준다.
- 공백을 넣으면 2가지 주요 요소가 확실히 나뉜다는 사실이 더욱 분명해진다.
- 메서드 :
lineWidthHistogram.addLine(lineSize, lineCount);
- 함수 이름과 이어지는 괄호 사이에는 공백을 넣지 않았다.
- 함수와 인수는 서로 밀접하기 때문이다. (오히려 공백을 넣으면 분리되어 보인다.)
- 대신, 각 인수는 서로 별개이기 때문에 쉼표 뒤에 공백을 넣어 준다.
(인수1, 인수2)
- 연산자 우선순위 :
- 승수 사이에는 공백이 없다 : 곱셈은 우선순위가 가장 높기 때문이다.
- 항 사이에는 공백이 들어간다 : 덧셈과 뺄샘은 우선순위가 곱셈보다 낮기 때문이다.
- 코드 형식을 자동으로 맞춰주는 도구들은 연산자 우선 순위를 고려하지 못해 해치는 경우가 많다.
가로 정렬
가로 정렬은 좋은 형식 구조는 아니다.
프로그래머는 명백하게 짧은 행을 선호한다.
가로 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다.
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
lineWidthHistogram.addLine(lineSize, lineCount);
double result = (-b - Math.sqrt(determinant)) / (2*a);
}
int lineSize = line.length();
- 연산자와 피연산자 사이에 공백을 준다.
- 공백을 넣으면 2가지 주요 요소가 확실히 나뉜다는 사실이 더욱 분명해진다.
lineWidthHistogram.addLine(lineSize, lineCount);
- 함수 이름과 이어지는 괄호 사이에는 공백을 넣지 않았다.
- 함수와 인수는 서로 밀접하기 때문이다. (오히려 공백을 넣으면 분리되어 보인다.)
- 대신, 각 인수는 서로 별개이기 때문에 쉼표 뒤에 공백을 넣어 준다.
(인수1, 인수2)
- 승수 사이에는 공백이 없다 : 곱셈은 우선순위가 가장 높기 때문이다.
- 항 사이에는 공백이 들어간다 : 덧셈과 뺄샘은 우선순위가 곱셈보다 낮기 때문이다.
- 코드 형식을 자동으로 맞춰주는 도구들은 연산자 우선 순위를 고려하지 못해 해치는 경우가 많다.
가로 정렬은 좋은 형식 구조는 아니다.
특정 구조를 강조하고자 가로 정렬을 이용하면 아래와 같은 코드가 된다.
public class FitNesseExpediter implements ResponseSender
{
private Socket socket;
private InputStream input;
private OutPutStream output;
...
public FitNesseExpediter(Socket s,
FitNesseContext context) throws Exception
{
socket = s;
input = s.getInputStream();
output = s.getOutputStream();
}
}
언뜻 유용하고 가독성이 좋은 것 같지만 사실을 그러지 않다.
- 변수 유형은 무시하고 변수 이름부터 읽게 된다.
- 할당 연산자는 보이지 않고 오른쪽 피연산자에 눈이 간다.
- 코드의 길이가 변경 된다면 모든 정렬을 다시 맞춰야 한다.
- 설상 가상으로 코드 형식을 자동으로 맞춰주는 도구는 대다수가 위와 같은 정렬을 무시한다.
public class FitNesseExpediter implements ResponseSender
{
private Socket socket;
private InputStream input;
private OutPutStream output;
...
public FitNesseExpediter(Socket s, FitNesseContext context) throws Exception
{
socket = s;
input = s.getInputStream();
output = s.getOutputStream();
}
}
- 정렬을 하지 않으면 오히려 중대한 결함을 찾아내기 쉽고 가독성이 좋아진다.
- 정렬이 필요한 정도로 목록이 길다면 문제는 목록의 길이지 정렬 부족이 아니다.
들여 쓰기
소스 파일은 윤곽도와 계층이 비슷하다.
계층에서 각 수준은 이름을 선언하는 범위이자 선언문과 실행문을 해석하는 범위이다.
- 범위(scope)로 이루어진 계층을 표현하기 위해 우리는 코드를 들여쓴다.
- 들여쓰기는 계층에서 코드가 자리잡은 수준에 비례한다.
- 클래스 정의처럼 파일 수준인 문장은 들여쓰지 않는다.
- 클래스 내 메서드는 클래스보다 한 수준 들여쓴다.
- 메서드 코드는 메서드 선언보다 한 수준 들여쓴다.
- 블록 코드는 블록을 포함하는 코드보다 한 줄 들여쓴다.
프로그래머는 들여쓰기 체계에 크게 의존한다.
왼쪽으로 코드를 맞춰 코드가 속하는 범위를 시각적으로 표현한다.
그러면 이 범위에서 저 범위로 재빨리 이동하기 쉬워진다.
들여쓰기 X 코드
public class FirstPage { private Settings settings = Settings.getInstance();
public void setAndPrintSettings() { settings.setDarkMode(true); settings.setFontSize(15);
System.out.println(settings.getDarkMode()+ " " + settings.getFontSize());}}
들여쓰기 O 코드
public class FirstPage {
private Settings settings = Settings.getInstance();
public void setAndPrintSettings() {
settings.setDarkMode(true);
settings.setFontSize(15);
System.out.println(settings.getDarkMode()+ " " + settings.getFontSize());
}
}
- 들여쓰기 파일은 구조가 한눈에 들어온다. (변수, 함수 등)
- 반면, 들여쓰기를 하지 않는 코드는 열심히 분석하지 않는 이상 이해하기도 어렵다.
- 하지만 때때로 간단한 제어문, 함수에서는 들여쓰기를 무시하고 싶은 경우도 있다.
- 모든 맥락은 같아야 하는법 : 아무리 짧은 코드라도 들여쓰기를 제대로 해주는 습관을 들이자
추가로
while (dis.read(buf, 0, readBufferSize) != -1)
;
- 빈
while문
이나for문
을 사용할 때는 빈 블록을 잘 들여쓰고 괄호로 감싼다.
팀 규칙
프로그래머마다 각자 선호하는 규칙이 있지만 팀에 속한다면 선호해야할 규칙은 팀 규칙이다.
프로그래머마다 각자 선호하는 규칙이 있지만 팀에 속한다면 선호해야할 규칙은 팀 규칙이다.
팀은...
- 한 가지 규칙에 합의해야하고 모든 팀원은 그 규칙을 따라야 소프트웨어가 일관적인 스타일을 보인다.
- 코딩 스타일을 의논하여 IDE Formatter로 지정하여 구현하는 것이 옳은 방식이다.
괄호를 어디에 넣을지, 네이밍은 어떻게 할지 등 - 좋은 소프트웨어 시스템은 읽기 쉬운 문서로 이뤄지고 문서는 스타일은 일관적이고 매끄러워야 한다.
- 한 소스 파일에서 봤던 형식이 다른 소스 파일에도 쓰이리라는 신뢰감을 독자에게 주어야한다.
밥 아저씨의 형식 규칙
사용하는 규칙은 간단하지만 코드 자체가 최고의 구현 표준 문서가 되는 것이다.
public class CodeAnalyzer implements JavaFileAnalysis {
private int lineCount;
private int maxLineWidth;
private int widestLineNumber;
private LineWidthHistogram lineWidthHistogram;
private int totalChars;
public CodeAnalyzer() {
lineWidthHistogram = new LineWidthHistogram();
}
public static List<File> findJavaFiles(File parentDirectory) {
List<File> files = new ArrayList<File>();
findJavaFiles(parentDirectory, files);
return files;
}
private static void findJavaFiles(File parentDirectory, List<File> files) {
for (File file : parentDirectory.listFiles()) {
if (file.getName().endsWith(".java"))
files.add(file);
else if (file.isDirectory())
findJavaFiles(file, files);
}
}
public void analyzeFile(File javaFile) throws Exception {
BufferedReader br = new BufferedReader(new FileReader(javaFile));
String line;
while ((line = br.readLine()) != null)
measureLine(line);
}
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
totalChars += lineSize;
lineWidthHistogram.addLine(lineSize, lineCount);
recordWidestLine(lineSize);
}
private void recordWidestLine(int lineSize) {
if (lineSize > maxLineWidth) {
maxLineWidth = lineSize;
widestLineNumber = lineCount;
}
}
public int getLineCount() {
return lineCount;
}
public int getMaxLineWidth() {
return maxLineWidth;
}
public int getWidestLineNumber() {
return widestLineNumber;
}
public LineWidthHistogram getLineWidthHistogram() {
return lineWidthHistogram;
}
public double getMeanLineWidth() {
return (double)totalChars/lineCount;
}
public int getMedianLineWidth() {
Integer[] sortedWidths = getSortedWidths();
int cumulativeLineCount = 0;
for (int width : sortedWidths) {
cumulativeLineCount += lineCountForWidth(width);
if (cumulativeLineCount > lineCount/2)
return width;
}
throw new Error("Cannot get here");
}
private int lineCountForWidth(int width) {
return lineWidthHistogram.getLinesforWidth(width).size();
}
private Integer[] getSortedWidths() {
Set<Integer> widths = lineWidthHistogram.getWidths();
Integer[] sortedWidths = (widths.toArray(new Integer[0]));
Arrays.sort(sortedWidths);
return sortedWidths;
}
}
Author And Source
이 문제에 관하여(형식 맞추기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@kwj1270/클린-코드-형식-맞추기
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
사용하는 규칙은 간단하지만 코드 자체가 최고의 구현 표준 문서가 되는 것이다.
public class CodeAnalyzer implements JavaFileAnalysis {
private int lineCount;
private int maxLineWidth;
private int widestLineNumber;
private LineWidthHistogram lineWidthHistogram;
private int totalChars;
public CodeAnalyzer() {
lineWidthHistogram = new LineWidthHistogram();
}
public static List<File> findJavaFiles(File parentDirectory) {
List<File> files = new ArrayList<File>();
findJavaFiles(parentDirectory, files);
return files;
}
private static void findJavaFiles(File parentDirectory, List<File> files) {
for (File file : parentDirectory.listFiles()) {
if (file.getName().endsWith(".java"))
files.add(file);
else if (file.isDirectory())
findJavaFiles(file, files);
}
}
public void analyzeFile(File javaFile) throws Exception {
BufferedReader br = new BufferedReader(new FileReader(javaFile));
String line;
while ((line = br.readLine()) != null)
measureLine(line);
}
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
totalChars += lineSize;
lineWidthHistogram.addLine(lineSize, lineCount);
recordWidestLine(lineSize);
}
private void recordWidestLine(int lineSize) {
if (lineSize > maxLineWidth) {
maxLineWidth = lineSize;
widestLineNumber = lineCount;
}
}
public int getLineCount() {
return lineCount;
}
public int getMaxLineWidth() {
return maxLineWidth;
}
public int getWidestLineNumber() {
return widestLineNumber;
}
public LineWidthHistogram getLineWidthHistogram() {
return lineWidthHistogram;
}
public double getMeanLineWidth() {
return (double)totalChars/lineCount;
}
public int getMedianLineWidth() {
Integer[] sortedWidths = getSortedWidths();
int cumulativeLineCount = 0;
for (int width : sortedWidths) {
cumulativeLineCount += lineCountForWidth(width);
if (cumulativeLineCount > lineCount/2)
return width;
}
throw new Error("Cannot get here");
}
private int lineCountForWidth(int width) {
return lineWidthHistogram.getLinesforWidth(width).size();
}
private Integer[] getSortedWidths() {
Set<Integer> widths = lineWidthHistogram.getWidths();
Integer[] sortedWidths = (widths.toArray(new Integer[0]));
Arrays.sort(sortedWidths);
return sortedWidths;
}
}
Author And Source
이 문제에 관하여(형식 맞추기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@kwj1270/클린-코드-형식-맞추기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)