본문 바로가기

Clean code

Clean Code 5장 - 형식 맞추기

자바 코딩을 하면서 여러가지 패턴을 적용하고, 인터페이스를 이용하는 등 멋지게 하고 싶어한다. 하지만, 그 전에 가장 기초적인게 코드의 형식을 맞추는 것이라 생각한다. IDE에서 Formatter 를 적용해 놓으면 팀내 들여쓰기 등 형식은 맞출 수 있다. 하지만 클래스의 세로 길이, 메소드의 세로 길이는 어느 정도가 적합할까?

 내가 만드는 기능이 복잡해서 클래스가 크다고? JUnit과 같은 큰 프로젝트도 대부분 200줄 정도의 파일로 구성되어 있다. 즉, 방금과 같은 변명은 핑계이다.

 

신문 기사처럼 작성하라

아주 좋은 신문 기사를 떠올려보라 .

  • 독자는 위에서 아래로 기사를 읽음
  • 첫 문단은 요약된 내용이다. 세세한 내용은 밑에서 나온다. -> 소스 파일 첫부분은 고차원 개념
  • 기사는 짧다

개념은 빈행으로 분리하라

package fitnesse.wikitext.widgets;

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();
	}
}
// 빈 행이 없는 경우, 가독성이 현저히 떨어진다
package fitnesse.wikitext.widgets;
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<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);
}

수직 거리

연관성이 깊은 두 개념이 멀리 떨어져 있으면 코드를 읽는 사람이 여기저기를 뒤져야 함

 

1. 변수 선언
 - 변수를 사용하는 위치에 최대한 가까이 선언 
 - 지역변수는 각 함수 맨 처음에 선언 ( 왜냐하면 우린 함수를 짧게 짧거다 )

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) {
          }
      }
  }

2. 인스턴스 변수

 - 이건 너무 당연해서 딱히.... 자바라면 클래스 맨위에 변수를 선언해둬야 한다.

 

3. 종속함수

 - 아까 말한 기사처럼 고수준 -> 저수준으로 짤 때, 함수도 같은 순서대로 선언하는 것이다. 그렇게 되면 당연히 위에서 아래로 읽혀지기 때문에 가독성이 좋아진다.

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;
      } 
  ...

4. 개념적 유사성

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);
} 

가로 들여쓰기 / 가로 밀집도

가로에 관련된 부분은 IDE에 설정된 Formatter에 의해 정해지기 때문에 공부하지 않아도 될 것 같다.