본문 바로가기
Architecture & Engineering/Clean Software

[Clean Code] 2. 주석, 형식 맞추기

by 신숭이 2023. 3. 1.

Clean Code 2. 주석, 형식 맞추기

 

클린코드 [2장. 의미 있는 이름], [3장. 함수]는 다소 심오하고 단번에 이해하기 어려운 이야기였다면 4, 5장은 비교적 단순한 내용이었다. 클린코드 [4장. 주석]과 [5장. 형식 맞추기]는 두 줄로 요약하면 다음과 같다.

주석은 안 쓰는게 좋다.
코드 작성은 비슷한 것끼리 가까운 곳에 위치하도록 하고, 가로 형식은 IDE의 도움을 받고 언어별 Linter와 Analyzer를 잘 활용하자.

 

 

주석 : 주석은 나쁜 코드를 보완하지 못한다. 새로 짜라

 

저자의 말은 빌리면 사실상 주석은 필요악이라고 한다. 주석도 유지보수할 자신이 없다면, 최대한 코드로 의도를 표현하라고 한다.

bad

// 직원에게 복지 혜택을 받을 자격이 있는지 검사한다.
if((employee.flags & HOURLY_FLAG) && (employee.age > 65))

 

good

if(employee.isEligibleForFullBenefits())

 

 

주석 : 좋은 주석의 종류

 

 

저자는 좋은 주석과 나쁜 주석의 예시를 모두 보여준다. 굳이 나쁜 주석을 볼 필요는 없으니, 좋은 주석만 보고 이 외의 모든 주석은 과감히 지우자. 

 

1. 법적인 주석

문서 상단에 다음과 같은 법적 내용을 명시하는 것은 필요한 주석이다. 이것도 가능하다면 표준 라이선스나 외부 문서를 참조하도록 하는 게 좋다.

// Copyright (C) 2003, 2004, 2005 by Object Mentor, Inc. All rights reserved
// GNU General Public License 버전 2 이상을 따르는 조건으로 배포한다

 

2. 정보를 제공하는 주석

대부분의 정보는 이름을 통해 제공하는게 제일 좋다. 다만 다음과 같은 경우에는 주석을 통해 정보를 제공하면 정말 유용하다. 
(흔한 패턴을 제외하곤 다른 개발자가 작성한 정규표현식을 바로 이해하기는 어렵기 때문)

// kk:mm:ss EEE, MMM dd, yyyy 형식이다
Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d* ");

 

3. 의도를 설명하는 주석

저자가 해당 코드를 어떤 의도를 가지고 작성했는지 적는 경우.

// 스레드를 대량 생성하는 방법. 어떻게든 경쟁 조건을 만들려 시도한다.
for (int i = 0; i < 25000; i++) {
    WidgetBuilderThread widgetBuilderThread = new WidgetBuilderThread(
		widgetBuilder, text, parent, failFlag);
    Thread thread = new Thread(widgetBuilderThread);
    thread.start();
}

 

4. 결과를 경고하는 주석

public static SimpleDateFormat makeStandardHttpDateFormat() {
    // SimpleDateFormat은 스레드에 안전하지 못함
    // 따라서 각 인스턴스를 독립적으로 생성해야한다
    SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
    df.setTimeZone(TimeZone.getTimeZone("GMT"));
    return df;
}

 

5. TODO 주석

최신 IDE는 TODO 주석을 별도로 추적할 수 있다. 프로그래머가 필요하다고 생각하지만 여러 가지 이유로 당장은 구현하기 어려울 때 남겨두는 주석이다. 다만 남용은 당연히 안 좋다.

 

 

 

주석 : 나쁜 주석

 

대부분의 주석이 나쁘다. 전부 지워버리면 된다.

  1. 주절거리는 주석 : 특별한 이유 없이, 의무감에 의해 또는 프로세스에 의해하라고 시키니까 하는 주석
  2. 같은 이야기를 반복하는 주석 : 이름으로 다 표현 충분히 되었는데, 또 한 번 설명하는 주석 (함수 상단에 파라미터를 주석으로 설명하는 행위)
  3. 오해할 여지가 있는 주석
  4. 의무적으로 다는 주석
  5. 주석으로 처리한 코드
     

 

 

 

형식 맞추기 : 적절한 줄 수를 유지하라 

 

저자는 가독성 있는 문서가 [작동하는 코드] 보다도 더 우선순위가 높다고 한다.

저자가 JUnit, Tomcat 등 대규모 시스템의 코드 파일의 길이를 조사해 본 결과 평균 65줄, 대부분의 파일이 100~400줄 내외로 되어있다고 한다. 이는 적은 줄 수로도 대규모 시스템을 구축할 수 있다는 것이다. 

엄격한 규칙은 아니지만 바람직한 규칙으로 경험을 토대로 생각해 보면 당연히 짧은 줄 수의 파일이 이해하기 더 쉬웠다.

 

 

 

형식 맞추기 : 수직 거리

 

기본적으로 개념은 빈 행으로 구분하고, 서로 연관이 있는 것은 근처에 작성하라고 한다. (저자는 이것을 세로 밀집도라고 한다) 특히 코드 가독성을 높이기 위해선 수직 거리를 잘 지키라고 하는데 내용은 다음과 같다. 이것만 잘 지켜진다면 다른 사람이 내 코드를 이해하는데 혹은 내가 나중에 내 코드를 볼 때 이해가 훨씬 빨라질 것. 

참고로 이 수직 거리는 연관 있는 것을 함께 작성해야 하는 중요성에 대해 계속 이야기한다. 특히 protected 변수는 피해야 하는 변수라고 저자는 강조한다.

 

1. 변수 선언 : 변수는 사용하는 위치에 최대한 가까이 선언한다.
* 인스턴스 변수(클래스 멤버 변수) : 클래스 맨 처음에 선언. 대부분의 클래스 메서드가 인스턴스 변수를 이용할 것이기 때문.

public static void readPreference() {
    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;
  }

 

3. 개념적 유사성 : 개념적으로 비슷한 함수끼리는 가까이 배치해 둔다. (JUnit 4.3.1의 예시)

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) {
        if(!condition) 
            fail(message);
    }

    static public void assertFalse(boolean condition) {
        assertFalse(null, condition);
    }

 

 

 

형식 맞추기 : 가로 형식

 

가로 형식은 한 줄에 몇 글자까지 적을지, 어떤 형식(Format)으로 작성할 지에 대해 다루는 이야기다. 통계에 따르면 대부분의 대규모 프로젝트에서 대부분의 코드가 20~60자 내외로 작성되어있다고 한다. 저자는 120자를 넘지 않도록 권장한다.

형식은 매개변수 뒤에 콤마(,) 뒤에 띄어쓰기를 한다거나, 들여 쓰기를 잘해야한다던가 하는 이야기인데 이는 언어마다 약간의 룰 차이가 있을 것 같아 다루지 않겠다. 최신 IDE의 Reformat 기능으로 알아서 포맷팅 및 들여쓰기를 해주고, 각 언어별 Linter와 Analyzer를 잘 활용하면 요새는 크게 신경 쓰지 않아도 될 것 같다. (그래도 새로운 언어를 배울 때는 Lint Rule 정도는 한 번 보는 게 좋을 것 같다)

댓글