Skip to content

5장 형식 맞추기

김윤수 edited this page Aug 1, 2022 · 1 revision

프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야한다.

형식을 맞추는 목적

코드 형식은 의사소통의 일환이며 전문 개발자의 일차적인 의무다.

  • 오늘 구현한 코드의 가독성은 앞으로의 코드에 영향을 끼친다.
  • 처음 구현 스타일과 가독성 수준은 유지보수와 확장성에 영향을 끼친다.
  • 원래 코드가 사라져도 스타일과 규율은 사라지지 않는다.

코드는 바뀔 가능성이 있지만 코드의 가독성은 계속 영향을 미친다.

적절한 행 길이를 유지하라

  • 큰 파일보다 작은 파일이 일반적으로 이해하기 쉽다.
  • 200줄 정도인 파일로도 커다란 시스템을 구축할 수 있다.
  • 반드시 지킬 엄격한 규칙은 아니지만 바람직한 규칙으로 삼자.

신문 기사 처럼 작성하라

소스 파일도 신문과 비슷하게 작성한다.

  • 소스 파일의 첫부분은 고차원 개념과 알고리즘을 설명한다.
  • 아래로 내려갈수록 의도를 세세하게 묘사한다.
  • 마지막에는 가장 저차원 함수와 세부 내역이 나온다.

개념은 빈 행으로 분리하라

거의 모든 코드는 왼쪽에서 오른쪽으로 위에서 아래로 읽는다.

  • 각 행은 수식이나 절을 나타낸다.
  • 일련의 행 묶음은 완결된 생각 하나를 표현한다.
  • 생각 사이는 빈 행을 넣어 분리해야 마땅하다.
package a

public class aa {

    public static final String b;
    private static final C c;

    public aa() {};

    public String d() {};

}

빈 행을 빼버리면 가독성이 현저하게 떨어진다.

package a
public class aa {
    public static final String b;
    private static final C c;
    public aa() {};
    public String d() {};
}

세로 밀집도

줄 바꿈이 개념을 분리한다면 세로 밀집도는 연관성을 의미한다.
즉, 서로 밀접한 코드 행은 세로로 가까이 놓여야 한다는 뜻이다.

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

수직 거리

서로 밀접한 개념은 가까이 둬야 한다.

  • 밀접한 두 개념은 세로 거리로 연관성을 표현한다.
  • 두 개념이 떨어져 있으면 읽는 사람이 여기저기 뒤지게 된다.

변수 선언

  • 변수는 사용하는 위치에 최대한 가까이 선언한다.
private void method1() {
    InputStream is = null;
    try{
        ...
    } 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 A {
    private String a;
    private String b;
    private String c;

    ...
}

종속 함수

  • 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다.
  • 가능하다면 호출하는 함수를 먼저 배치한다.
public class A {
    private String a;

    public void method1() {
        a = method2();
    }

    public String method2() {
        return "A.a";
    }
    ...
}

개념적 유사성

개념적 친화도가 높을수록 가까이 배치한다.

  • 한 함수가 다른 함수를 호출해 생기는 직접적인 종속성
  • 변수와 그변수를 사용하는 함수의 종속성
  • 비슷한 동작을 수행하는 일군의 함수의 종속성
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();
    totalChars += lineSize;
    lineWidthHistogram.addLine(lineSize, lineCount);
    recordWidfestLine(lineSize);
}
  • 할당 연산자 int lineSize = line.length();
    • 강조하려 앞뒤에 공백을 줬다.
    • 공백을 넣으면 두 가지 주요 요소가 확실히 나뉜다.
  • 함수 이름 lineWidthHistogram.addLine(lineSize, lineCount);
    • 이름과 괄호 사이에 공백을 넣지 않았다.
    • 함수와 인수는 서로 밀접하기 때문이다. (넣으면 별개로 보인다)
    • 괄호 안 인수는 공백으로 분리해 별개라는 사실을 보여준다. (lineSize, lineCount)
  • 연산자 우선순위 (-b - Math.sqrt(determinant)) / (2*a)
    • 승수 사이에는 공백이 없다. (우선순위가 가장 높음)
    • 항 사이에는 공백이 들어간다. (덧셈 뺄셈은 우선순위가 낮음)

가로 정렬

특정 구조를 강조하기 위해 가로 정렬을 사용했다.

public class FitNesseExpediter 
{
    private   Socket        socket;
    private   InputStream   input;
    private   Outputstream  output;
    protected long          requestParsingTimeLimit;
    private   long          requestProgress;
    ...

    public FitNesseExpediter(Socket           s, 
                             FitNesseContext  context) throws Exception
   {
      socket   =     s;
      input    =     s.getInputStream();
      output   =     s.getOutputStream();
   }
}
  • 이는 유용하지 않다.
  • 변수 유형은 무시하고 변수 이름부터 읽게 된다.

정렬이 필요할 정도로 목록이 길다면 문제는 목록 길이지 정렬 부족이 아니다.

들여쓰기

  • 소스 파일은 유곽도와 계층이 비슷하다.
  • 범위로 이뤄진 계층을 표현하기 위해 우리는 코드를 들여쓴다.

프로그래머는 들여쓰기 체계에 크게 의존한다.

public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseserver(FitNesseContext context) { this.context = context; }public void serve(Socket s) { serve(s, 10000); } public void serve(Socket s, long requestTimeout) { try { ... } catch { ... }}}

들여쓰지 않는다면 코드를 알아 볼 수 없다.

public class FitNesseServer implements SocketServer { 
   private FitNesseContext context; 

   public FitNesseserver(FitNesseContext context) {
   this.context = context; 
   }

   public void serve(Socket s) {
       serve(s, 10000);
   }

   public void serve(Socket s, long requestTimeout) {
       try {
           ...
       }
       catch {
           ...
       }
   }
}

가짜

빈 while문이나 for문은 새 행에다 들여써서 ;을 넣어준다.

while (dis.read(buf, 0, readBufferSize) != -1)
;

팀 규칙

프로그래머라면 각자 선호하는 규칙이 있다. 하지만 팀에 속한다면 팀 규칙을 따라야한다.

  • 팀은 한가지 규칙에 합의해야 하고 그 규칙을 따라야한다.
  • 소프트웨어 시스템은 읽기 쉬운 문서로 이뤄지고 스타일은 일관적이고 매끄러워야 한다.
  • 한 소스 파일에서 봤던 형식이 다른 소스파일에도 쓰이리라는 신뢰감을 주어야한다.

밥 아저씨의 형식 규칙