개발자라면 협업 시, 사용하는 언어의 코드 컨벤션을 지켜야 합니다.
이전까지는 느낌상 이렇게 하면 되겠다는 생각으로 개발을 했었는데,
굉장히 위험한 생각인 것 같아 정리하게 되었습니다.
이 포스트는 Google Java Style Guide 의 내용을 토대로 썼습니다.
1. 소개
Java 소스 파일은 아래의 규칙을 준수하는 경우에만 Google 스타일로 설명이 됩니다. Google에서는 어떤 코딩 스타일을 따르고 있는지 알아봅니다.
1.1 용어 참고
달리 명시되지 않는 한,
- 클래스라는 용어는 "일반"클래스, 열거 형 클래스, 인터페이스 또는 주석 유형(@interface)을 의미하기 위해 포괄적으로 사용됩니다.
- 클래스의 멤버라는 용어는 중첩 된 클래스, 필드, 메서드 또는 생성자를 의미하기 위해 포괄적으로 사용됩니다.
- 주석이라는 용어는 항상 구현 주석을 의미합니다. "Documentation comments"라는 이름을 사용하지 않고 "Javadoc"이라는 일반적인 용어를 사용합니다.
2. 소스파일 기본
2.1 파일이름
- 클래스 이름과 동일하게 대소문자를 구별해서 작성. 확장자는 java.
- ex) Car class가 구현된 소스파일 이름: Car.java
2.2 소스파일 인코딩
- 인코딩은 UTF-8로 통일.
2.3 공백문자
- 스페이스키만 허용.
2.4 특수 문자
- 특수문자를 사용할 때는 (\b, \t, \n, \f, \r, ", ' and \)을 사용.
- special escape sequence를 참고
2.5 유니코드
- 코드의 가독성을 높일 수 있다면 유니코드를 사용해도 됨.
3. 소스파일 구조 (소스 파일내 코드 선언 순서)
소스코드는 위 형태로 선언되야함. 각 섹션 사이에는 공백 라인이 하나 들어감.
3.1 라이센스
- 라이센스 또는 저작권 정보가 파일에 속하면 기입.
3.2 Package 문
- 아무리 길어도 줄바꿈을 하지 않음.
3.3 Import 문
- 와일드 카드(e.g. )를 쓰지 않음, Package 문과 동일하게 *줄바꿈** 하지 않음.
- 그룹핑을 해서 순서에 맞춰 작성. 다른 그룹간에는 공백라인을 한 줄 추가.
- 그룹 이름 및 순서:
- static import
- non - static imports
3.4 Class 선언
- 최상위 클래스만 선언
- 클래스 멤버(1.1 참고)의 순서는 절대적인 것이 없음. 하지만 이들의 순서가 논리적이여야 함.
- 새로운 메소드가 추가되었다고 해서 클래스의 가장 마지막에 구현하는 것은 논리적이지 않음.
- 동일한 메소드명 (생성자들, 오버라이딩된 메소드들) 은 한 곳에 모음.
4. 포맷팅
4.1 중괄호
- K & R 스타일을 따름
- 본문이 비어 있거나 단일 문만 포함하는 경우에도 if, else, for, do 및 while 문과 함께 사용
- 틀린 예
if (money > price) return true; else return false;
- 옳은 예
if (money > price) { return true; } else { return false; }
- 여는 중괄호 뒤에는 코드가 없어야 함
- 닫는 중괄호 뒤에는 코드가 없어야 함
- 문장에 닫는 중괄호만 있는 케이스는 함수가 끝나거나 제어문이 끝날 때.
- 코드가 없는 메소드의 경우는 그냥 닫아도 괜찮음.
// This is acceptable void doNothing() {} // This is equally acceptable void doNothingElse() { }
4.2 들여쓰기
- 스페이스키 2개로 정의
4.3 한 문장에 하나의 statement
- 각 문장 뒤에는 줄 바꿈을 함
4.4 줄길이 제한: 100
- 한 라인의 문자는 100개만 씀. 그 보다 길 경우 다음 라인으로 내림.
4.5 Line-wrapping( 줄 바꿈 )
- 코드의 길이가 페이지 넓이를 넘어갈 때, 하나의 문장을 두 문장 이상으로 나눠서 표현하는 것.
- 절대적인 법칙은 없고 상황에 따라서 적절하게 사용하면 됨
- 줄 바꿈의 일반적인 이유는 열 제한이 넘치지 않도록하는 것이지만 실제로 열 제한에 맞는 코드도 작성자의 재량에 따라 줄 바꿈 될 수 있음.
- 줄바꿈을 하기전, 메소드 추출을 이용해도 됨
4.5.1 언제 줄바꿈을 하는지
- a) 문장을 대임 연산자가 아닌 곳에서 잘라야 할 경우 심볼 압에서 내림.
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream() .filter(i -> i % 2 == 0) .distinct() .forEach(System.out::println); @Override public String toString() { return "Order{" + "memberId=" + memberId + ", itemName='" + itemName + '\'' + ", itemPrice=" + itemPrice + ", discountPrice=" + discountPrice + '}';
- b) 대입 연산자에서 잘라야 할 경우 대입 연산자 뒤에서 문장을 내림
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
- c) 함수호출의 경우 '('는 첫 문장에 두고 나머지를 다음 문장으로 내림
Assertions.assertThat( order.getDiscountPrice()).isEqualTo(1000);
- d) 콤마 '.'의 경우, 앞의 식별자와 동일한 단어로 취급
public void plus(int a, int b, int v, int d, int e, int f, int g) {
4.5.2 Line-wrapping 된 문장, 기본적으로 2번 이상의 들여쓰기
여러 문장이 연속해서 내려올 경우 첫 번째 내려온 문장과 동일한 들여쓰기를 유지
4.6 공백
4.6.1 공백 라인
- 클래스 멤버들을 구별하는 데 사용( 메소드, 생성자, 멤버 변수 )
- 멤버변수의 경우, 사이에 코드가 없다면 굳이 공백라인을 넣지 않아도 됨.
- 메소드 내부에서 논리적으로 그룹핑 되는 부분
4.6.1 공백 문자
- if, for, catch 와 ‘(‘ 사이에 공백문자
- else, catch와 ‘}’ 사이에 공백문자
- ‘,’, ‘:’, ‘;’ 다음 이나 타입 캐스트시의 ‘)’ 다음에 공백문자
- 연산자 앞 뒤로는 공백문자 삽입
- 연산자와 비슷한 심볼에서도 앞 뒤로 공백문자 삽입
Boolean test = true; if (test) { //... } else { //... } int testNum = 2 + 1 * (4 / 2) - 25; for (int i = 0; i < 3; i++) { //... }
4.8 나머지
4.8.1 Enum 클래스
- 열거 형 상수 뒤에 오는 각 쉼표 뒤에 줄 바꿈은 선택 사항.
- 추가 빈 줄 (일반적으로 하나만)도 허용됨
private enum Answer { YES { @Override public String toString() { return "yes"; } }, NO, MAYBE }
- 메서드가없고 상수가 없는 열거 형 클래스는 선택적으로 배열 이니셜 라이저 인 것처럼 형식화 될 수 있음
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
4.8.2 변수 선언
4.8.2.1 선언 당 하나의 변수
- 모든 변수 선언 (필드 또는 로컬)은 하나의 변수만 선언 e.g. int a, b; //사용되지 않음
int a;
int b;
4.8.2.1 필요할 때 선언
- 무조건적으로 시작부분에 시작하는 것은 아님
- 지역 변수는 범위를 최소화하기 위해 처음 사용되는 지점에 가깝게 선언됨
- 지역 변수 선언에는 일반적으로 이니셜 라이저가 있거나 선언 직후에 초기화됨.
4.8.3 배열
4.8.3.1 배열 초기화는 블록 형태도 가능
- 밑의 형식이 다가능함. (전체 목록이 아님)
new int[] { new int[] {
0, 1, 2, 3 0,
} 1,
2,
new int[] { 3,
0, 1, }
2, 3
} new int[]
{0, 1, 2, 3}
4.8.3.2 C 스타일 배열 선언 안됨
- 대괄호는 변수가 아닌 유형의 일부를 형성.
- String args []가 아니라 String [] args로 선언.
4.8.4 Switch 문
- switch문 자체가 레거시일 수 있으니, 안 알아 봄.
4.8.5 애노테이션
- 클래스, 메서드 또는 생성자에 적용되는 애노테이션은 문서 블록 바로 뒤에 나타나며, 각 애노테이션은 자체 줄에 나열됨. (즉, 한 줄에 하나의 주석)
- 이러한 줄 바꿈은 들여 쓰기 수준이 증가하지 않음
@Override
@Nullable
public String getNameIfPresent() { ... }
- 이것도 가능
@Override public int hashCode() { ... }
- 이것도 가능
@Partial @Mock DataLoader loader;
4.8.6 주석 스타일
/*
* This is // And so /* Or you can
* okay. // is this. * even do this. */
*/
4.8.7 Modifier 순서
public protected private abstract default static final transient volatile synchronized native strictfp
4.8.8 숫자 리터럴
3000000000l 이 아닌 3000000000L 사용
5. 네이밍
- 모든 식별자들은 ASCII와 숫자 값만 사용해야 함.
- 식별자에 prefix나 suffixes는 사용하지 않음. 즉, 아래와 같은 형태의 식별자는 사용되지 않음.
name_
mName
s_name
kName
5.2 식별자 유형별 규칙
5.2.1 Package 명
- 모두 소문자로 기술. 단어가 달라지더라도 무조건 소문자를 사용.
com.example.deepspace (O) com.example.deepSpace (X) com.example.deep_space (X)
5.2.2 Class 명
- UpperCarmelCase를 사용. 이는 대문자로 시작하고 단어가 바뀔 때마다 다시 대문자로 표시. e.g. HelloWorld.java
- 테스트 클래스의 경우 마지막에 Test로 끝나도록 함. (HashIntegrationTest)
5.2.3 메소드 명
- lowerCarmelCase를 사용. 이는 소문자로 시작하고 단어가 바뀔 때마다 다시 대문자로 표시. e.g. printHelloWorld()
- 테스트 클래스의 경우 마지막에 Test로 끝나도록 함. (HashIntegrationTest)
5.2.4 상수 명
- 상수
- 내용이 완전히 불변하고 메서드에 감지 가능하고 사이드 이펙트가 없는 정적 최종 필드.
- 여기에는 프리미티브, 문자열, 불변 유형 및 불변 유형의 불변 컬렉션이 포함.
- CONTANT_CASE 방식을 사용. 이는 모두 대문자를 사용하며 단어 사이에 밑줄을 표시. 당연히 명사나 명사구여야 한다
// Constants
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final ImmutableMap<String, Integer> AGES = ImmutableMap.of("Ed", 35, "Ann", 32);
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }
// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<String>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final ImmutableMap<String, SomeMutableType> mutableValues =
ImmutableMap.of("Ed", mutableInstance, "Ann", mutableInstance2);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};
5.2.5 멤버변수명 / 인자명 / 로컬변수명
- lowerCarmerCase를 사용.
- 메소드명과 다른 점은 동사가 아닌 명사라는 점. 한문자는 피함.
6. 프로그래밍 관례
6.1 @Override는 무조건 사용
- 오버라이딩이 된 모든 경우에@Override는 필수로 기입
- 부모 클래스의 메소드를 재정의 하거나 인터페이스를 구현했을 때도 마찬가지.
- 다만 부모 쪽에서 @Deprecated를 선언했을 경우에는 자식 쪽에서 @Override를 생략가능.
6.2 모든 예외 처리
- 모든 예외는 무시하지 말고 처리.
- 만약 예외를 처리하지 않을 거면 그 이유에 대해서 명확하게 주석을 담
- 테스트 코드에서는 필요시 무시 가능.
6.3 Static 멤버 접근
- 클래스 명으로 접근.
Foo aFoo = ...;
Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad
somethingThatYieldsAFoo().aStaticMethod(); // very bad
7. javadoc
/**
* Multiple lines of Javadoc text are written here,
* wrapped normally...
* @author Livenow
*/
public class JavaDoc{
/**
* 곱셈을 합니다.
* @param a
* @param b
* @return int
*/
public int method(int a, int b) {
return a * b;
}
}
- /** 다음은 공백.
- 문단과 문단 사이에는 공백라인이 들어가고 @ 시작하기 전에도 공백라인이 들어감.
- @param, @return, @throws, @deprecated 순으로 사용.
- 설명은 무조건 기술해야 하며 한 문장을 넘어가면 4개 이상의 스페이스로 들여쓰기 함.
결론
대충 생각했던 Convention에 이렇게 많은 규약이 있는지 몰랐습니다.
협업을 진행하면서 꼭 생각을 하며 코드를 작성해야겠습니다.
댓글