기여자를 위한 AOSP 자바 코드 스타일

이 페이지는 Android 오픈소스 프로젝트(AOSP)에 자바 코드를 기여할 때 엄격하게 지켜야 할 규칙을 다룹니다. 일반적으로 이러한 규칙을 준수하지 않으면 Android 플랫폼 참여가 허용되지 않습니다. 기존 코드의 경우 이러한 규칙을 준수하지 않는 경우도 있다는 점을 Google에서도 인지하고 있으나, 신규 코드의 경우에는 반드시 준수해야야 합니다. 더 포용적인 생태계를 조성하기 위해 사용하거나 사용하지 않아야 할 용어의 예는 사용자를 존중하는 코딩을 참고하세요.

일관성 유지하기

가장 간단한 규칙은 일관성 유지하기입니다. 코드를 수정 중인 경우 잠시 시간을 내어 주변 코드를 살펴보고 스타일을 결정하세요. 이 코드가 if 절 주변의 공간을 사용하는 경우 기여자도 마찬가지로 공간을 사용해야 합니다. 코드 주석에 별 상자가 있는 경우에는 기여자의 주석 주변에도 별 상자가 있어야 합니다.

스타일 가이드라인의 목적은 공통의 코드 언어를 통해 형태보다는 코드가 의미하는 바가 무엇인지 독자가 쉽게 이해하도록 돕는 데 있습니다. 이 페이지에서는 기여자가 이러한 공통의 언어를 이해하도록 돕기 위해 전체적인 스타일 규칙을 제시하지만 코딩별로 자체적인 스타일 역시 중요합니다. 파일에 추가하는 코드가 기존 코드와 과하게 다를 경우에는 혼란을 야기할 수 있습니다. 이러한 불상사를 피할 수 있도록 노력하세요.

자바 언어 규칙

Android는 아래에 설명되는 추가 규칙을 포함하는 표준 자바 코딩 규칙을 따릅니다.

예외를 무시하면 안 됨

다음과 같이 예외를 무시하는 코드를 작성하고 싶을 수 있습니다.

  void setServerPort(String value) {
      try {
          serverPort = Integer.parseInt(value);
      } catch (NumberFormatException e) { }
  }

이렇게 하지 마세요. 코드에서 이러한 오류 조건이 발생할 일이 없거나 이 조건을 처리하는 일이 중요하지 않다고 생각할 수도 있지만, 이러한 예외유형을 무시하는 것은 코드에 언제 터질지 모르는 시한폭탄을 설치하는 것과 같습니다. 코드의 모든 예외는 원칙대로 처리해야 하며 구체적인 처리 방법은 경우에 따라 다릅니다.

"빈 캐치(catch) 절을 맞닥뜨리게 되면 누구나 오싹한 기분이 들 겁니다. 그렇게 비워 두는 것이 맞는 경우도 분명 있지만, 적어도 먼저 고민해 보아야 합니다. 자바에서는 오싹한 기분을 결코 무시해선 안 되니까요." - 제임스 고슬링

허용되는 대안(선호 순서대로)은 다음과 같습니다.

  • 메서드 호출자에게 예외를 반환합니다.
      void setServerPort(String value) throws NumberFormatException {
          serverPort = Integer.parseInt(value);
      }
    
  • 추상화 계층에 적절한 새 예외를 반환합니다.
      void setServerPort(String value) throws ConfigurationException {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new ConfigurationException("Port " + value + " is not valid.");
        }
      }
    
  • 오류를 말끔하게 처리하고 catch {} 블록의 적절한 값을 대체합니다.
      /** Set port. If value is not a valid number, 80 is substituted. */
    
      void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            serverPort = 80;  // default port for server
        }
      }
    
  • 예외를 캐치하고 RuntimeException의 새 인스턴스를 반환합니다. 이는 위험하므로 이 오류가 발생했을 때의 올바른 조치가 비정상 종료라고 확신하는 경우에만 실행하세요.
      /** Set port. If value is not a valid number, die. */
    
      void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new RuntimeException("port " + value " is invalid, ", e);
        }
      }
    
  • 예외를 무시하는 것이 적절하다고 확신하는 경우에는 마지막 수단으로 이를 무시할 수도 있지만 합당한 이유를 주석으로 남겨야 합니다.
    /** If value is not a valid number, original port number is used. */
    
    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            // Method is documented to just ignore invalid user input.
            // serverPort will just be unchanged.
        }
    }
    

일반 예외를 캐치하면 안 됨

예외를 캐치할 때 다음과 같이 간편하게 일반적인 예외만 캐치하고 싶은 유혹이 들 수도 있습니다.

  try {
      someComplicatedIOFunction();        // may throw IOException
      someComplicatedParsingFunction();   // may throw ParsingException
      someComplicatedSecurityFunction();  // may throw SecurityException
      // phew, made it all the way
  } catch (Exception e) {                 // I'll just catch all exceptions
      handleError();                      // with one generic handler!
  }

이렇게 하지 마세요. 거의 모든 경우에 일반적인 Exception 또는 Throwable을 캐치하는 것이 부적절합니다(Error 예외를 포함하는 Throwable은 가급적 피하는 것이 좋음). 이 경우 전혀 예상치 못했던 예외(예: ClassCastException 같은 런타임 예외)가 앱 수준의 오류 처리에 휘말리게 되므로 위험합니다. 이는 코드의 장애 처리 속성을 애매하게 만듭니다. 즉, 호출하려는 코드에 누군가가 새로운 예외 유형을 추가할 경우 오류를 다른 방식으로 처리해야 한다는 점을 컴파일러가 알려주지 않습니다. 대부분의 경우 서로 다른 예외 유형을 동일한 방식으로 처리하면 안 됩니다.

이러한 규칙의 보기 드문 예외는 각종 오류를 캐치하여 UI에 표시되지 않게 하거나 일괄 작업의 실행을 유지하는 것이 좋은 테스트 코드와 상위 수준 코드입니다. 이러한 경우에는 일반 Exception(또는 Throwable)을 캐치하고 오류를 적절히 처리할 수 있습니다. 이렇게 하기 전에 매우 신중하게 생각해야 하며, 여기서 이러한 방식이 왜 안전한지를 설명하는 주석을 달아야 합니다.

일반 예외 캐치의 대안:

  • 각 예외를 다중 캐치 블록의 일부로 따로 캐치합니다. 예를 들면 다음과 같습니다.
    try {
        ...
    } catch (ClassNotFoundException | NoSuchMethodException e) {
        ...
    }
  • 코드를 리팩터링하여 다중 시도 블록을 포함하는 더욱 세분화된 오류 처리 방식을 확보합니다. 파싱에서 IO를 분할하고 각 사례에서 오류를 따로 처리합니다.
  • 예외를 다시 반환합니다. 많은 경우 이 수준에서는 어차피 예외를 캐치할 필요가 없으므로 메서드가 반환하게 놔두면 됩니다.

예외는 언제나 존재한다는 사실을 잊지 마세요. 예외를 캐치하지 않는다고 컴파일러에서 알림 메시지가 표시되더라도 심각하게 여길 필요가 없습니다. 컴파일러는 그저 코드의 런타임 문제를 더욱 쉽게 캐치할 수 있도록 도와준 것뿐입니다.

파이널라이저를 사용하면 안 됨

파이널라이저는 객체가 가비지로 수집될 때 코드 청크를 실행할 수 있는 한 가지 방법입니다. 파이널라이저는 특히 외부 리소스 등을 정리할 때 유용할 수 있지만 파이널라이저가 언제 호출될지 또는 호출이 되기는 할지는 보장되지 않습니다.

Android에서는 파이널라이저를 사용하지 않습니다. 대부분의 경우 좋은 예외 처리 방식을 대신 사용할 수 있습니다. 파이널라이저가 절대적으로 필요한 경우 close() 메서드 또는 유사 메서드를 정의하고 이러한 메서드를 정확히 언제 호출해야 할지를 문서화하세요(예시가 필요한 경우 InputStream 참조). 이 경우에는 로그가 넘칠 것으로 예상되지 않는 이상 파이널라이저에서 간단한 로그 메시지를 출력하는 것이 권장됩니다(필수는 아님).

가져오기 정규화

패키지 foo에서 클래스 Bar를 사용하고 싶은 경우 이를 가져올 수 있는 두 가지 방법이 있습니다.

  • import foo.*;

    가져오기 구문의 개수가 감소할 가능성이 있습니다.

  • import foo.Bar;

    어떤 클래스가 사용되고 있는지 명확히 알려주며 유지관리자가 코드를 더 쉽게 읽을 수 있습니다.

import foo.Bar;는 모든 Android 코드를 가져오는 데 사용하세요. 자바 표준 라이브러리(java.util.*, java.io.* 등) 및 단위 테스트 코드(junit.framework.*)의 명시적 예외가 있습니다.

자바 라이브러리 규칙

Android의 자바 라이브러리 및 도구 사용에 관한 규칙이 있습니다. 일부 사례에서는 규칙이 중요한 방식으로 변경되었으며 기존 코드에서 지원 중단된 패턴이나 라이브러리를 사용할 수도 있습니다. 이러한 코드를 작업할 때는 기존 스타일을 계속 사용해도 괜찮습니다. 하지만 새 구성요소를 생성할 때는 지원 중단된 라이브러리를 사용하면 안 됩니다.

자바 스타일 규칙

Javadoc 표준 주석 사용

모든 파일 상단에는 저작권 고지에 이은 패키지 및 가져오기 구문이 있어야 하며(각 블록이 빈 행으로 구분됨), 마지막으로 클래스나 인터페이스 선언이 있어야 합니다. Javadoc 주석에 클래스 또는 인터페이스의 역할을 설명하세요.

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.foo;

import android.os.Blah;
import android.view.Yada;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Does X and Y and provides an abstraction for Z.
 */

public class Foo {
    ...
}

작성하는 모든 클래스 및 사소하지 않은 공개 메서드에는 클래스 또는 메서드의 역할을 설명하는 문장이 1개 이상 포함된 Javadoc 주석이 있어야 합니다. 이 문장은 3인칭 서술형 동사로 시작해야 합니다.

/** Returns the correctly rounded positive square root of a double value. */

static double sqrt(double a) {
    ...
}

또는

/**
 * Constructs a new String by converting the specified array of
 * bytes using the platform's default character encoding.
 */
public String(byte[] bytes) {
    ...
}

모든 Javadoc에서 'sets Foo'를 언급할 경우 setFoo() 같은 사소한 get 및 set 메서드와 관련된 Javadoc을 작성할 필요가 없습니다. 메서드가 좀 더 복잡한 작업을 실행할 경우(제약 적용 또는 중요한 부작용 발생)에는 이를 문서화해야 합니다. 속성 'Foo'의 의미가 불확실한 경우 이를 문서화해야 합니다.

작성되는 모든 메서드는 공개든 아니든 Javadoc을 통한 이점을 얻게 됩니다. 공개 메서드는 API의 일부이므로 Javadoc을 필요로 합니다. Android는 현재 Javadoc 주석 작성을 위한 특정 스타일을 적용하고 있지는 않지만 Javadoc 도구의 문서 주석 작성 방법의 안내를 따라야 합니다.

짧은 메서드 작성

가능한 한 메서드는 작고 집중된 형태로 유지해야 합니다. 긴 메서드가 적절할 때도 있다는 점을 Google에서도 잘 인지하고 있으며, 따라서 메서드 길이에 엄격한 제한은 없습니다. 메서드가 40행 정도를 초과하는 경우에는 프로그램의 구조가 손상되지 않는 선에서 이를 분할할 수 있는지 생각해 보세요.

표준 위치에 필드 정의

필드는 파일 상단이나 필드를 사용하는 메서드 바로 앞에 정의하세요.

변수 범위 제한

로컬 변수의 범위는 최소로 유지하세요. 이렇게 하면 코드의 가독성과 유지관리성을 개선하고 오류 발생 확률을 줄일 수 있습니다. 모든 변수의 사용을 포괄하는 가장 안쪽의 블록에 각 변수를 선언하세요.

로컬 변수는 처음 사용되는 시점에 선언하세요. 거의 모든 로컬 변수 선언에 초기화 프로그램이 포함되어야 합니다. 아직 변수를 합리적으로 초기화하는 과정에 관한 충분한 정보가 없는 경우 정보를 확보할 때까지 선언을 보류하세요.

예외는 try-catch 구문입니다. 변수가 확인된 예외를 반환하는 메서드의 반환값으로 초기화된 경우에는 try 블록 내에서 초기화되어야 합니다. 값을 try 블록 외부에서 사용해야 하는 경우에는 아직 합리적으로 초기화할 수 없는 try 블록 전에 선언되어야 합니다.

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

하지만 이러한 경우조차 try-catch 블록을 메서드에 캡슐화하는 방식으로 피할 수 있습니다.

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 구문 자체에 루프 변수를 선언하세요.

for (int i = 0; i < n; i++) {
    doSomething(i);
}

for (Iterator i = c.iterator(); i.hasNext(); ) {
    doSomethingElse(i.next());
}

가져오기 구문 순서

가져오기 구문의 순서는 다음과 같습니다.

  1. Android 가져오기
  2. 타사에서 가져오기(com, junit, net, org)
  3. javajavax

IDE 설정과 정확히 일치하려면 가져오기가 다음과 같아야 합니다.

  • 각 그룹 내에서 알파벳 순서여야 하며 소문자 앞에 대문자가 옵니다(예: Z가 a보다 앞에 위치).
  • 각 주요 그룹 간에 빈 행으로 구분합니다(android, com, junit, net, org, java, javax).

원래는 순서에 관한 스타일 요구사항이 없었습니다. 즉, IDE가 항상 순서를 변경하거나 IDE 개발자가 자동 가져오기 관리 기능을 사용 중지하고 가져오기를 수동으로 관리해야 했습니다. 이에 관한 의견은 부정적이었습니다. 자바 스타일이 요청되었을 때는 선호되는 스타일이 매우 다양했으며, Android로 내려왔을 때는 단순히 '순서를 선택하고 일관성을 유지'해야 했습니다. 따라서 Google은 스타일을 선택하고 스타일 가이드를 업데이트한 다음 IDE가 이를 따르도록 했습니다. IDE 사용자가 코드를 작업할 때는 모든 패키지의 가져오기가 추가적인 엔지니어링 작업 없이 이러한 패턴과 일치해야 합니다.

다음과 같은 이유로 이 스타일을 선택했습니다.

  • 사람들이 먼저 보고 싶어 하는 가져오기가 주로 상단에 위치(android)
  • 사람들이 가장 보고 싶어 하지 않는 가져오기가 주로 하단에 위치(java)
  • 사람이 스타일을 쉽게 따를 수 있어야 함
  • IDE가 스타일을 따를 수 있어야 함

정적 가져오기를 다른 모든 가져오기 위해 배치하는 순서는 일반 가져오기와 같은 방식으로 지정되었습니다.

들여쓰기에 공백 사용

Google은 블록 및 never 탭에 (4)개의 공백 들여쓰기를 사용합니다. 확실하지 않은 경우 주변 코드와 일관성을 유지하세요.

Google은 함수 호출 및 할당을 비롯한 행 래핑에 (8)개의 공백 들여쓰기를 사용합니다.

권장함

Instrument i =
        someLongExpression(that, wouldNotFit, on, one, line);

권장하지 않음

Instrument i =
    someLongExpression(that, wouldNotFit, on, one, line);

필드 이름 지정 규칙 준수

  • 비공개 및 비정적 필드 이름은 m으로 시작합니다.
  • 정적 필드 이름은 s로 시작합니다.
  • 다른 필드는 소문자로 시작합니다.
  • 마지막 정적 필드(상수, 절대 변경 불가능)는 ALL_CAPS_WITH_UNDERSCORES입니다.

예를 들면 다음과 같습니다.

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자입니다. 이 규칙에 관한 많은 논의가 이루어지고 있지만 현재로서는 최대 길이가 100자로 계속해서 제한되며, 다음과 같은 예외가 적용됩니다.

  • 주석 행에 명령어 예시 또는 100자를 초과하는 리터럴 URL이 포함된 경우, 이 라인은 쉽게 자르고 붙여넣을 수 있도록 100자를 초과할 수 있습니다.
  • 가져오기 행은 제한을 초과할 수 있으며, 이는 사람이 이 행을 거의 보지 않기 때문입니다(도구 작성도 간소화함).

표준 자바 주석 사용

동일한 언어 요소의 경우 주석이 다른 수식어보다 앞서야 합니다. 단순 마커 주석(예: @Override)은 언어 요소와 같은 행에 나열할 수 있습니다. 주석 또는 매개변수화된 주석이 여러 개인 경우에는 알파벳순으로 행당 하나씩 나열합니다.

자바의 세 가지 사전 정의된 주석에 관한 Android의 표준 관행은 다음과 같습니다.

  • 주석이 지정된 요소의 사용이 권장되지 않을 때마다 @Deprecated 주석을 사용하세요. @Deprecated 주석을 사용하는 경우에는 @deprecated Javadoc을 함께 사용해야 하며, 이를 통해 대체 구현의 이름이 지정되어야 합니다. 또한 @Deprecated 메서드가 계속해서 작동해야 한다는 점을 기억해야 합니다. @deprecated Javadoc 태그가 지정된 기존 코드가 보인다면 @Deprecated 주석을 추가하세요.
  • 메서드가 슈퍼클래스의 선언 또는 구현을 재정의할 때마다 @Override 주석을 사용하세요. 예를 들어 @inheritdocs Javadoc 태그를 사용하며 인터페이스가 아닌 클래스에서 파생되는 경우에는 메서드가 상위 클래스의 메서드를 재정의한다는 주석도 달아야 합니다.
  • @SuppressWarnings 주석은 경고를 제거하는 것이 불가능한 상황에서만 사용해야 합니다. 경고가 이 '제거 불가' 테스트를 전달하면 모든 경고에 코드의 실질적인 문제가 반영되도록 @SuppressWarnings 주석을 사용해야 합니다.

    @SuppressWarnings 주석이 필요한 경우에는 '제거 불가' 조건을 설명하는 TODO 주석을 접두사로 사용해야 합니다. 이렇게 하면 보통 어색한 인터페이스가 있는 잘못된 클래스가 식별됩니다. 예:

    // TODO: The third-party class com.third.useful.Utility.rotate() needs generics
    @SuppressWarnings("generic-cast")
    List<String> blix = Utility.rotate(blax);
    

    @SuppressWarnings 주석이 필요한 경우 주석이 적용되는 소프트웨어 요소를 구분할 수 있도록 코드를 리팩터링해야 합니다.

약어를 단어로 취급

이름 지정 변수, 메서드 및 클래스의 단어를 약어 및 축약어로 취급하여 이름의 가독성을 높이세요.

좋음 나쁨
XmlHttpRequest XMLHTTPRequest
getCustomerId getCustomerID
class Html class HTML
String url String URL
long id long ID

JDK 및 Android 코드베이스 모두 약어와 일치하지 않으므로 주변 코드와 일관성을 유지하기가 거의 불가능합니다. 따라서 약어는 항상 단어로 취급하세요.

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가 '미래 날짜에 뭔가를 실행' 형식인 경우에는 매우 구체적인 날짜('2005년 11월까지 수정') 또는 매우 구체적인 이벤트('모든 프로덕션 믹서가 프로토콜 V7을 이해한 후에는 이 코드를 삭제')를 포함해야 합니다.

로깅을 가급적 자제

로깅은 필요하기는 하지만 성능에 부정적인 영향을 미치고 합리적으로 간결하지 않을 경우 유용성을 잃을 수 있습니다. 로깅 기능은 5가지 수준의 로깅을 제공합니다.

  • ERROR: 치명적인 상황, 즉 사용자에게 결과가 표시되며 일부 데이터를 삭제하거나 앱을 제거하거나 데이터 파티션을 삭제하거나 기기 전체를 다시 플래시하지 않으면 복구가 불가능한 경우(또는 더 좋지 않은 경우)에 사용합니다. 이 수준은 항상 로깅됩니다. ERROR 수준의 일부 로깅을 정당화하는 문제는 통계 수집 서버에 보고하기에 적절합니다.
  • WARNING: 예기치 못한 심각한 상황, 즉 사용자에게 결과가 표시되지만 앱 대기 또는 재시작부터 새로운 버전의 앱 다시 다운로드 또는 기기 재부팅까지 명시적 작업을 실행하면 데이터 손실 없이 복구 가능한 경우에 사용합니다. 이 수준은 항상 로깅됩니다. WARNING 수준의 로깅을 정당화하는 문제는 통계 수집 서버에 보고하는 것을 고려할 수도 있습니다.
  • INFORMATIVE: 참고할 만한 상황, 즉 반드시 오류인 것은 아니지만 광범위한 영향을 미칠 수 있는 상황이 감지된 경우를 언급할 때 사용합니다. 이러한 조건은 해당하는 도메인에서 가장 권한이 높다고 생각되는 모듈에서 로깅해야 합니다(권한이 없는 구성요소에 의한 중복 로깅을 피하기 위해). 이 수준은 항상 로깅됩니다.
  • DEBUG: 기기의 현재 상황, 즉 예상치 못한 동작을 조사하고 디버그하는 데 관련이 있을 수 있는 경우를 추가로 언급할 때 사용하세요. 구성요소와 관련된 정보를 수집하는 데 필요한 내용만 로깅합니다. 로그 대부분이 디버그 로그인 경우 상세 로깅을 사용해야 합니다.

    이 수준은 출시 빌드 시에도 로깅되며 if (LOCAL_LOG) 또는 if LOCAL_LOGD) 블록으로 둘러싸야 합니다. LOCAL_LOG[D]는 클래스 또는 하위 구성요소에서 정의되므로 그러한 모든 로깅이 사용 중지될 가능성이 있습니다. 따라서 if (LOCAL_LOG) 블록에는 활성 논리가 없어야 합니다. 로그와 관련된 모든 문자열 빌드 역시 if (LOCAL_LOG) 블록 내에 배치해야 합니다. 문자열 빌드가 if (LOCAL_LOG) 블록의 외부에서 발생하도록 만드는 원인이 될 경우 로깅 호출을 메서드 호출로 리팩터링하지 마세요.

    여전히 if (localLOGV)를 언급하는 일부 코드가 있습니다. 이 역시 허용되는 코드로 간주되지만 이름은 표준이 아닙니다.

  • VERBOSE: 다른 모든 경우에 사용하세요. 이 수준은 디버그 빌드 시에만 로깅되며, 기본으로 컴파일될 수 있도록 if (LOCAL_LOGV) 블록(또는 상응 대상)으로 둘러싸면 안 됩니다. 모든 문자열 빌드는 출시 빌드에서 제거되며 if (LOCAL_LOGV) 블록 내에 나타나야 합니다.

참고

  • VERBOSE 수준 외의 지정된 모듈 내에서는 가능하다면 오류를 한 번만 보고해야 합니다. 모듈 내의 단일 함수 호출 사슬 내에서는 가장 안쪽의 함수만 오류를 반환해야 하며, 같은 모듈의 호출자는 문제를 격리하는 데 크게 도움이 되는 경우에만 일부 로깅을 추가해야 합니다.
  • VERBOSE 수준 외의 모듈 사슬에서는 하위 수준 모듈이 더 높은 수준의 모듈에서 유입되는 잘못된 데이터를 감지하는 경우 하위 수준 모듈이 이 상황을 DEBUG 로그에만 로깅해야 합니다. 단, 로깅이 달리 제공되지 않는 정보를 호출자에게 제공해야 합니다. 구체적으로는 예외가 반환되었거나(예외는 모든 관련 정보를 포함해야 함) 로깅되고 있는 유일한 정보가 오류 코드에 포함된 경우에는 상황을 로깅할 필요가 없습니다. 이는 프레임워크와 앱 간의 상호작용에서 특히 중요하며, 타사 앱에 의해 발생하여 프레임워크에 의해 제대로 처리되는 조건은 DEBUG 수준보다 높은 로깅을 트리거하면 안 됩니다. INFORMATIVE 수준 이상에서 로깅을 트리거해야 하는 유일한 상황은 모듈이나 앱이 자체 수준 또는 더 낮은 수준에서 비롯된 오류를 감지하는 경우입니다.
  • 일반적으로 일부 로깅을 정당화할 조건이 자주 발생할 가능성이 높은 경우에는 속도 제한 메커니즘을 구현하여 로그가 같거나 매우 유사한 정보로 이루어진 다수의 중복 사본으로 넘치지 않도록 하는 것이 좋을 수 있습니다.
  • 네트워크 연결 끊김은 일반적이고 온전히 예상되는 상황으로 간주되므로 이유 없이 로깅하면 안 됩니다. 앱 내에 결과를 초래하는 네트워크 연결 끊김은 결과를 출시 빌드에 로깅해야 할 만큼 갑작스럽고 심각한지에 따라 DEBUG 또는 VERBOSE 수준에서 로깅되어야 합니다.
  • 타사 앱에 액세스 가능하거나 타사 앱을 대신하여 액세스 가능한 파일 시스템에 전체 파일 시스템을 두는 것은 INFORMATIVE보다 높은 수준에서 로깅하면 안 됩니다.
  • 신뢰할 수 없는 출처(공유 저장공간의 파일이나 네트워크 연결을 통해 유입되는 데이터 포함)에서 오는 잘못된 데이터는 예상된 것으로 간주되며, 잘못된 것으로 감지되는 경우(그리고 로깅이 최대한 제한되어야 하는 경우) DEBUG보다 높은 수준에서 로깅을 트리거하면 안 됩니다.
  • String 객체에서 사용되는 + 연산자는 기본 버퍼 크기(16자) 및 잠재적으로 다른 임시 String 객체를 사용하여 암시적으로 StringBuilder 인스턴스를 생성합니다. 따라서 명시적으로 StringBuilder 객체를 생성하는 것은 기본 + 연산자를 사용하는 것보다 비싸지 않으며 훨씬 더 효율적일 수 있습니다. Log.v()를 호출하는 코드는 로그를 읽을 수 없는 경우에도 문자열 빌드를 포함하여 출시 빌드에서 컴파일 및 실행됩니다.
  • 다른 사람이 읽어야 하고 출시 빌드에서 사용 가능해야 하는 로깅은 암호화 없이 간결해야 하며 이해할 수 있어야 합니다. 여기에는 DEBUG 수준까지의 모든 로깅이 포함됩니다.
  • 가능하면 로깅을 한 행에 유지합니다. 행 길이는 80~100자까지 허용됩니다. 가능하면 약 130~160자(태그 길이 포함)를 넘지 않도록 하세요.
  • 로깅이 성공하면 VERBOSE보다 높은 수준에서는 사용하지 마세요.
  • 재현하기 어려운 문제를 진단하기 위해 임시 로깅을 사용하는 경우 DEBUG 또는 VERBOSE 수준으로 유지하고 컴파일 시 사용 중지할 수 있는 if 블록으로 묶습니다.
  • 로그를 통한 보안 유출을 조심하세요. 비공개 정보는 로깅하지 마세요. 특히 보호된 콘텐츠에 관한 정보는 로깅하지 마세요. 이는 프레임워크 코드를 작성할 때 특히 중요한데, 그 이유는 비공개 정보나 보호된 콘텐츠의 여부를 미리 알기가 쉽지 않기 때문입니다.
  • System.out.println()(또는 네이티브 코드의 경우 printf())을 사용하지 마세요. System.outSystem.err/dev/null로 리디렉션되므로 출력 구문에서 눈에 보이는 효과가 없습니다. 하지만 이러한 호출에 관해 발생하는 모든 문자열 빌드는 계속해서 실행됩니다.
  • 로깅의 황금률은 다른 로그가 사용자의 로그를 푸시할 수 없는 것처럼 사용자의 로그 역시 다른 로그를 버퍼 밖으로 불필요하게 푸시할 수 없다는 것입니다.

Javatests 스타일 규칙

테스트 메서드 이름 지정 규칙을 따르고 밑줄을 사용하여 테스트 중인 대상을 테스트 중인 구체적인 사례에서 구분하세요. 이 스타일을 사용하면 테스트 중인 사례를 더 쉽게 확인할 수 있습니다. 예:

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