티스토리 뷰

반응형

안드로이드 개발자를 위한 코드 스타일 가이드라인 2편입니다.

메소드를 짧게 유지하라
가능한 한도 내에서 메소드는 한 주제에 맞게 짧게 작성되어야 합니다. 어떤 경우에는 긴 메소드를 작성하는 것이 옳기 때문에 메소드 길이에 정확히 정해진 제한은 없습니다. 한 메소드의 길이가 40줄을 넘어가게 되면 전체 프로그램의 구조를 해치지 않는 선에서 코드를 나눌 수 있는지 생각해야 합니다.

해진 위치에 필드를 정의하라
필드는 파일의 상단이나 그 필드를 사용하는 메소드 바로 직전에 정의되어야 합니다.

변수의 스코프(scope)를 제한하라
지역(local) 변수의 스코프는 최소한으로 유지되어야 하는데 그럼으로써 코드의 가독성과 유지보수의 편이성이 높아질 뿐만 아니라 에러의 가능성도 낮출 수 있기 때문입니다. 모든 변수는 그 변수가 사용되는 범위를 모두 감쌀 수 있는 가장 안쪽의 블록에서 선언되어야 합니다.

지역 변수는 처음 사용되는 시점에 선언되어야 합니다. 선언과 동시에 초기화도 해야 합니다. 변수를 선언하는 시점에서 어떤 값으로 초기화할지 충분한 정보가 없다면 주어지는 시점까지 변수의 선언을 미뤄야 합니다.

이 규칙에 한 가지 예외가 있다면 try-catch 문을 사용할 때 입니다. 만약 어떤 변수가 예외 처리되는 메소드의 반환값으로 초기화 될 경우 트라이 블록 안에서 초기화되어야 합니다. 만약 다음 예와 같이 그 변수가 트라이 블록 바깥에서 사용되어야 한다면 해당 변수는 트라이 블록 이전에 초기화 없이 선언될 수 있습니다.
// Instantiate class cl, which represents some sort of Set 
Set s = null;
try {
    s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
    throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
    throw new IllegalArgumentException(cl + " not instantiable");
}

// Exercise the set 
s.addAll(Arrays.asList(args));

하지만 이런 경우에도 트라이-캐치 블록 자체를 메소드에 인캡슐레이션함으로써 변수를 미리 선언하는 것을 피할 수 있습니다.
Set createSet(Class cl) {
    // Instantiate class cl, which represents some sort of Set 
    try {
        return (Set) cl.newInstance();
    } catch(IllegalAccessException e) {
        throw new IllegalArgumentException(cl + " not accessible");
    } catch(InstantiationException e) {
        throw new IllegalArgumentException(cl + " not instantiable");
    }
}

...

// Exercise the set 
Set s = createSet(cl);
s.addAll(Arrays.asList(args));

반복문에 사용되는 변수는 꼭 그러지 말아야 할 이유가 없는 한 반복문 내부에서 선언되어야 합니다.
for (int i = 0; i < n; i++) {
    doSomething(i);
}

아래도 마찬가지입니다.
for (Iterator i = c.iterator(); i.hasNext(); ) {
    doSomethingElse(i.next());
}
    
임포트import문의 순서를 지켜라
임포트문은 다음의 순서로 정렬합니다.
1. 안드로이드 임포트
2. 써드파티 임포트(com, junit, net, org)
3. java와 javax

IDE 설정과 정확히 맞추기 위한 순서는 다음과 같습니다:
□ 각각의 그룹 내에서는 알파벳순으로 정렬합니다. 대문자가 소문자 앞에 옵니다. (즉 Z가 a보다 앞입니다.)
□주요 그룹은 한 줄 띄웁니다. (android, com, junit, net, org, java, javax).

그렇게 정해진 스타일은 다음의 목적을 만족시키고자 합니다:
□ 사람들이 먼저 보기 원하는 임포트는 상위에 위치합니다. (예: android)
□ 그다지 볼 필요가 없는 임포트는 하단에 위치합니다. (예: java)
□ 사람들이 스타일을 쉽게 따를 수 있습니다.
□ 다양한 IDE도 스타일을 쉽게 따를 수 있습니다.

임포트 순서의 경우 많은 사람들이 그렇게 중요하게 생각하지 않는 문제인 만큼 알아서 판단하되 일관성만 유지하면 됩니다.

들여쓰기에는 스페이스를 사용하라
블록 들여쓰기에는 4개의 스페이스를 사용합니다. 다른 편집기에서도 동일한 들여쓰기 효과를 보기 위해서 꼭 탭 대신 스페이스를 사용해야 합니다.

함수 호출이나 값을 할당할 때 등에 줄바꿈을 할 때는 8개의 스페이스를 사용합니다. 아래는 올바른 사용 예입니다.
Instrument i =
        someLongExpression(that, wouldNotFit, on, one, line);

그리고 아래는 잘못된 예입니다.
Instrument i =
    someLongExpression(that, wouldNotFit, on, one, line);
         
필드 네이밍 컨벤션을 따르라
□ 퍼블릭이나 스태틱이 아닌 필드의 이름은 m으로 시작합니다.
□ 스태틱 필드 이름은 s로 시작합니다.
□ 다른 필드는 소문자로 시작합니다.
□ 퍼블릭 스태틱 필드(상수)는 모두 대문자에 언더스코어를 사용합니다.

아래는 올바른 사용 예입니다.
public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

정해진 중괄호 스타일을 사용하라
다음과 같이 혼자서 한 줄을 차지하는 중괄호를 사용하지 말고 이전 줄의 코드에 붙여 사용합니다.
class MyClass {
    int func() {
        if (something) {
            // ...
        } else if (somethingElse) {
            // ...
        } else {
            // ...
        }
    }
}

조건문을 사용할 때는 중괄호를 사용하는 것이 원칙입니다. 단, 조건문의 모든 내용이 한 줄로 기술이 가능하면, 한줄에 쓰는 것도 가능합니다. 아래는 가능한 예입니다.
if (condition) {
    body(); 
}

아래의 예도 가능합니다.
if (condition) body();

하지만 아래는 잘못된 예입니다.
if (condition)
    body();  // bad!
   
행 길이를 제한하라
코드의 한 행의 길이는 최대 100글자로 제한되어야 합니다.

예외: 커맨드나 URL을 포함한 코멘트의 경우 한 줄에 100글자가 넘더라도 복사, 붙여넣기 등의 편이를 위해 길이를 제한하지 않습니다.

예외: 임포트문의 경우 사람이 직접 코드를 읽는 경우가 적기 때문에 100글자 이상의 코드를 허용합니다.

자바 표준 주석(annotation)을 사용하라
주석은 다른 modifier보다 선행합니다. @Override 같은 단순한 주석은 다른 언어 요소(language element)와 같은 줄에 위치해도 됩니다. 주석이 여러 개이거나 파라미터를 받는 주석인 경우, 알파벳 순서대로 한줄에 하나씩 적는 것이 원칙입니다.

자바에서 선정의 된(predefined) 세 가지 주석에 대해 안드로이드가 정한 기준은 다음과 같습니다.

@Deprecated: @Deprecated 주석은 주석 처리된 요소의 사용이 자제되어야 할 때 사용됩니다. 만약 @Deprecated 주석을 사용하겠다면 @deprecated 자바독(Javadoc) 태그를 만들어 대안으로 사용될 메소드를 명시해야 합니다. 추가적으로 @Deprecated 처리된 메소드들은 여전히 동작해야 합니다.
기존 코드에서 @deprecated 자바독 태그를 본다면 @Deprecated 주석을 달아야합니다.

@Override: @Override 주석은 슈퍼클래스에 구현 또는 선언되어있는 메소드를 오버라이드(override)하는 경우에 사용됩니다.
예를 들어, @inheritdocs 자바독 태그를 사용하고, 인터페이스가아닌 클래스로부터 상속을 받을 경우엔 각 메소드가 부모 클래스의 메소드를 오버라이드 한다는 것을 주석으로 표시해야 합니다.

@SuppressWarnings: @SuppressWarnings 주석은 경고(warning)을 없애는 것이 불가능한 상황에서 사용되어야 합니다. 발생하는 모든 경고가 코드 상에 실재하는 문제를 반영하기 위해 필요한 주석입니다. @SuppressWarnings 주석은 "경고를 제거하는 것이 불가능한" 상황을 설명하는 `TODO` 주석과 같이 작성되어야 하는데 이를 통해 어떤 클래스가 이상한 인터페이스를 가지고 있는지 파악할 수 있습니다. 예를 들면 다음과 같습니다.

// TODO: The third-party class com.third.useful.Utility.rotate() needs generics 
@SuppressWarnings("generic-cast")
List<String> blix = Utility.rotate(blax);
```
@SuppressWarnings 주석이 들어가야 하는 경우엔, 주석이 적용되는 범위의 코드는 리팩토링(refactoring)되어야 합니다.
             

문자어(acronym)을 일반 단어로 취급하라
변수와 메소드, 클래스의 이름을 지을 때 두 문자어와 약어(abbreviation)를 일반적인 단어로 취급합니다. 더 가독성이 좋은 이름을 지을 수 있습니다.

좋은 예) XmlHttpRequest
나쁜 예) XMLHTTPRequest

좋은 예) getCustomerId
나쁜 예) getCustomerID

좋은 예) class Html
나쁜 예) class HTML

좋은 예) String url
나쁜 예) String URL

좋은 예) long id
나쁜 예) long ID

JDK와 안드로이드 코드는 이 두 문자어와 관련된 문제에서 굉장히 일관적이지 못한 편입니다. 따라서 사실상 여러분이 접할 모든 코드에서 이 규칙을 지키는 것이 불가능합니다. 그런 만큼 모든 두 문자어를 일반 단어로 취급하도록 노력해야 합니다.

TODO 코멘트를 사용하라
TODO 코멘트는 일시적이거나 단기적인 해결책, 완벽하진 않지만 우선은 괜찮은 코드 등에 사용할 수 있습니다.

TODO는모두 대문자로 작성되어야 하며 항상 콜론(:)이 뒤따라야 합니다.
// TODO: Remove this code after the UrlTable2 has been checked in.

또 다른 예입니다.
// TODO: Change this to use a flag instead of a constant.

"언제까지 어떤 것을 해야 한다"는 식으로 TODO를 작성할 경우 아주 자세한 일정이나 "2018년 11월까지 수정", 자세한 할 일 "모든 관계자들이 7버전의 프로토콜을 이해한 뒤에야 이 코드를 삭제"등을 병기합니다.
    
로그는 아껴 써라
로그는 필수적인 작업이지만 짜임새있게 코딩하지 않으면 성능에 부정적인 영향을 주고 그 순기능을 쉽게 잃어버리게 됩니다.

자바의 로그 퍼실리티(log facility)는 아래 5단계의 로그를 지원합니다.
1. ERROR: 일반 사용자가 바로 발견할 수 있는 결과가 나거나 따로 어떤 데이터를 지우거나, 애플리케이션을 삭제하거나, 데이터 파티션을 싹 날리거나 아니면 기기를 초기화 하거나(아니면 그보다 더 나쁜 상황이거나) 하는 정도로 아주 치명적인 일이 발생했을 때 사용하는 단계입니다. 이 단계의 로그는 어느 상황에서든(릴리즈 빌드에서든 디버그 빌드에서든) 필수적이며 통계 서버에 기록을 고려해볼 만한 대상이다.

2. WARNING: 심각하거나 예상하지 못한 일이 발생했을 때, 즉 사용자에게 가시적인 결과가 있지만 별도의 액션(좀 기다리거나 앱을 재시작하거나 새 버전으로 업그레이드 하거나 기기를 재시작하는 정도) 또는 데이터 손실이 없이 복구가 가능한 상황 등에 사용하는 단계입니다. 이 단계의 로그 또한 어느 상황에서든 필수적이며 통계 서버에 기록을 고려해볼 만한 대상입니다.

3. INFORMATIVE: 꼭 에러까지는 아니더라도 그 효과가 다방면으로 퍼지는 상황처럼 많은 수의 사람들이 주지하고 있어야 하는 상황에 사용하는 단계입니다. 다른모듈에 의한 중복 로그를 방지하기 위해 이 단계의 로그는 해당 영역에서 로그를 하기에 가장 적절한 권한을 가지는(most authoritative) 모듈에 의해 작성되는 것이 좋습니다. 마찬가지로 항상 기록되어야 하는 로그입니다.

4. DEBUG: 예상하지 못한 결과를 디버그하거나 조사할 때 관련이 있을 수도 있는 부분에 대한 기록에 사용하는 단계입니다. 해당 지점에 대해 충분한 정보를 얻는데 필요한 정도로만 기록해야 하며 전체 로그 양에 비해 이 단계의 로그가 너무 많아질 경우엔 VERBOSE 로그를 사용하는 것을 고려해야 합니다. 릴리즈 빌드에서도 필요하다면 기록되어야 하지만 `if (LOCAL_LOG)` 또는 `if (LOCAL_LOGD)` 블록등으로 감싸서 `LOCAL_LOG[D]`를 정의하는 클래스나 서브컴포넌트 등이 원한다면 모든 디버그 로그를 비활성화할 수 있어야 합니다. 따라서 `if (LOCAL_LOG)` 블록 안에는 유효한 로직이 들어가서는 안되고 로그에 사용되는 모든 문자열은 `if (LOCAL_LOG)` 블록 안에 들어가 있어야 합니다. 아직도 `if (localLOGV)` 같은 코드가 존재하는데 비록 표준에서 벗어나는 네이밍이지만 허용 가능한 코드입니다.

5. VERBOSE: 나머지 모든 것을 로그할 때 쓰는 단계입니다. 이 로그는 디버그 빌드에서만 기록되어야 하고 기본적으로 컴파일에서 제외되어야 하기 때문에 if (LOCAL_LOGV) 블록 또는 그와 동치인 블록 등으로 둘러싸여야 합니다. DEBUG와 마찬가지로 릴리즈 빌드에서는 모든 문자열이 제외되기 때문에 로그와 관련된 문자열들은 if (LOCAL_LOGV) 블록 내부에 작성되어야 합니다.

VERBOSE 로그가 아닌 이상, 가능한 한 하나의 모듈 안에서 특정 에러는 단 한 번만 보고되어야 합니다. 한 모듈 내부의 일련의 함수 호출 과정에서는 가장 내부에 위치하는 함수만이 에러를 반환해야 하고 그 함수를 호출하는 동일 모듈 내의 다른 부분에서는, 그렇게 하는 것이 문제를 찾는데 충분한 도움이 되는 경우에만 필요한 로그를 덧붙이는 형식이 되어야 합니다.

모듈 체인에서 상위 레벨의 모듈에서 온 데이터가 하위 레벨 모듈에서 유효하지 않은 것으로 확인될 때엔, 로그가 되지 않고서는 함수를 호출한 대상이 그 사실을 알 수 없는 경우에만 DEBUG 로그가 필요합니다. 구체적으로 익셉션이 발생하는 경우와(익셉션 자체가 모든 관련된 정보를 담고 있습니다.) 로그 정보가 오직 에러 코드를 담고 있는 경우에는 로그를 할 필요가 없습니다. 이 규칙은 프레임워크와 애플리케이션 사이의 상호작용에 있어 특히 중요한데, 써드파티 애플리케이션에서 발생하는 예외 상황을 프레임워크가 올바르게 처리를 하는 상황에선 DEBUG 단계를 넘는 로그가 필요하지 않습니다. INFORMATIVE 이상의 로그가 필요한 유일한 상황은 모듈이나 애플리케이션이 자신이 속한 레벨 또는 그보다 하위 레벨에서 오는 오류를 발견했을 때입니다.

어떤 로그가 다발적으로 발생할 가능성이 정당화되는 상황이라면, 같은 (또는 아주 비슷한) 정보가 여러 번 기록되어 로그가 넘쳐나는 현상을 방지하기 위해 비율 제한(rate-limiting) 메커니즘을 구현하는 것을 고려하면 좋습니다.

네트워크와 연결이끊기는 상황은 자주 발생하는 일이기 때문에 불필요하게 로그되어서는 안됩니다. 네트워크의 연결이 끊김으로써 앱 내부에서 어떤 결과를 가져오는 경우에는 DEBUG 또는 VERBOSE 단계의 로그가 적절합니다. 릴리즈 빌드에서도 로그 될 필요가 있을 만큼 심각하거나 예상하지 못한 상황이 발생하면 그 정도에 따라 로그 단계를 설정하면 됩니다.

써드파티 애플리케이션을 위해 존재하는 또는 써드파티 애플리케이션이 접근 가능한 파일시스템에 대해서는 INFORMATIVE보다 상위 단계에서 로그하면 안됩니다.

신뢰하지 못하는 모든 소스(공유 저장소의 모든 파일들과 네트워크를 통한 모든 수신 데이터를 포함)로부터 온 데이터가 유효하지 않은 것은 충분히 발생할 가능성이 있는 상황이기 때문에 DEBUG보다 상위 단계에서 로그되어서는 안 되며 로그를 하는 경우에도 최대한 제한되어야 합니다.

+ 연산자가문자열String에 사용되면 내부적으로 16글자의 기본 버퍼 사이즈를 갖는 StringBuilder를 포함해 임시적으로 사용되는 다른 여러 문자열 객체를 만든다는 사실에 유념해야 합니다. 사실 기본 + 연산자를 사용하는 것보다 명시적으로(explicitly) 스트링빌더를 정의하는 것이 훨씬 더 효율적일 수 있습니다. 다른 사람들이 보도록 릴리즈 빌드에서 발생시키는 모든 DEBUG 단계까지의 로그는 그 의미를 숨기지 않으면서 다른 사람들이 이해할 수 있게끔 간결해야 합니다.

가능하다면 로그는 한줄에서 끝나는 것이 좋습니다. 80~100자 정도의 로그까지는 충분히 허용될 만하며 태그 길이를 포함해 130~160자 정도되는 로그는 되도록 피해야 합니다.

성공을 보고하는 로그는 VERBOSE보다 상위의 단계에서 사용되어서는 안됩니다.

재생산하기 어려운 이슈를 점검하기 위한 일시적인 로그는 DEBUG 또는 VERBOSE 단계에 머물러야 하며, 컴파일 타임에 전부 비활성화 될 수 있게 조건문if 블록으로 감싸야 합니다.

로그를 통한 보안 문제에 신경써야 합니다. 보호되어야 하는 정보는 물론 개인 정보 역시 로그에 포함되어서는 안됩니다.

이 규칙은 특히나 프레임워크 코드를 짤 때 더욱 중요한데 프레임워크 코드를 짤 때는 어떤 정보가 개인 정보인지, 또는 보호되어야 하는 정보인지 미리 알기가 쉽지 않기 때문입니다.

System.out.println() 또는 printf() 등은 절대 사용하지 말아야 합니다. System.out과 System.err은 /dev/null로 리다이렉트되기 때문에 아무런 가시적 효과를 얻을 수 없음에도 여전히 유효하게 실행되는 문장들이기 때문입니다.

*로그의 황금률은 다른 사람들의 로그가 당신의 로그를 방해해도 안 되듯이 당신의 로그가 다른 사람들의 로그를 불필요하게 가로 막아서는 안 된다는 것이다.*

일관성을 지하라
마지막으로 항상 코드의 일관성을 유지해야 합니다. 코드를 수정할 일이 생긴다면 몇 분이라도 투자해 주변 코드를 살펴보고 수정할 때 어떤 스타일을 사용할 것인지 결정해야 합니다. 어떤 코드가 if문 주변으로 스페이스를 사용했다면 여러분도 그렇게 해야 합니다. 그 코드의 주석이 별표로 만들어진 상자 안에 들어 있다면 당신의 주석 또한 별표 상자 안에 작성해야 한다는 뜻입니다.
스타일 가이드라인의 포인트는 코딩에 있어 공통적인 어휘를 사용함으로써 사람들이 여러분이 어떻게(how) 말하고 있는지보다 무엇을(what) 말하고 있는지에 집중하게 하는 것입니다. 이 전반적인 스타일 규칙으로 공통적으로 사용될 어휘를 제시했지만 특정한 경우의 스타일 또한 중요합니다. 새롭게 짜넣은 코드가 기존의 코드와 심하게 다른 스타일이라면 다른 사람들이 코드를 읽어가면서 적지 않은 불편을 겪게 됩니다.
        
테스트 메소드 네이밍 컨벤션을 따르라
테스트 메소드에 이름을 붙일 때는 무엇을 테스트하고 있는지와 테스트의 대상이 되는 특정 경우를 구분하기 위해 언더스코어_를 사용하면 됩니다. 이 스타일을 따르면 어떤 케이스를 테스트하는지 알아보기 쉽습니다.

예:
testMethod_specificCase1 testMethod_specificCase2

void testIsDistinguishable_protanopia() {
    ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)
    assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))
    assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))


반응형
댓글
공지사항
최근에 올라온 글