백준이든 api를 만들든 나는 항상 코드가 잘 돌아가기만 하면 된다고 생각했다. 그래서 몇 중 for문이든 그냥 쓰고, 함수로 빼는 일도 잘 없었다. 하지만 팀원들은 내 코드를 보고 이해하기 어려워서 손도 못대겠다고 했다.. 😥😥😥
잘 짠 코드는 남이 알아보기 쉽게 작성한 코드라고 한다. 기능이 잘 돌아가는 것 외에 코드를 어떻게 깔끔하게, 남이 알아보기 쉽게 만들 수 있는지 고민을 해볼 단계가 된 것 같다.
이번에 우테코를 지원하였는데, 프리코스의 문제 요구사항 중 코드컨벤션 맞추기가 있었다. 이번에 Google Java Style Guide를 정리하며 자바 코드 컨벤션을 공부하고, 컨벤션에 맞추어 코드를 짜는 연습을 해보려고 한다.
https://google.github.io/styleguide/javaguide.html
- 이 사이트는 영어 원문이다
- 영어를 잘 하지 못하기 때문에 영어원문과 해석본을 비교하면서 공부를 해보려고 한다. 해석된 사이트 2가지는 다음과 같음
https://github.com/JunHoPark93/google-java-styleguide
https://newwisdom.tistory.com/m/96
들어가기 전
저는 제가 보기 쉽게 정리하였고, 글을 읽고 제가 생각하여 작성한 예시가 포함되어 있습니다.
틀리는 경우가 있을 수 있으니, 보다 정확한 자료를 원하신다면 위 링크를 참고해주세요!
1. 용어
- class는 일반적인 class, enum, interface 또는 어노테이션 유형(@Interface) 을 의미하는 포괄적인 의미로 사용
- member: 중첩된 클래스, 필드, 메서드 또는 생성자 의미
- comment: 주석 (Javadoc)
2. 소스 파일 기본사항
- 파일 이름은 최상위 클래스의 대소문자 구분 이름과 .java 확장자로 구성
- 파일인코딩 = UTF-8
- 특수문자
- 공백문자: tab문자는 들여쓰기에 사용되지 않음
- Non-ASCII문자: 실제 유니 코드 문자 또는 동등한 유니 코드 이스케이프가 사용됨
3. 소스 파일 구조
- 소스파일 순서(하나의 빈 줄이 각 섹션을 구분함)
라이센스 또는 저작권 정보 | |
Package 구문 | 열 제한이 없음 |
Import 구문 |
static이나 와일드카드 import 사용x |
줄바꿈 금지 | |
단일 블록에서 모든 static imports -> 단일 블록의 모든 non-static imports 순서로 씀 | |
정확히 하나의 최상위 Class |
클래스 선언
- 메서드를 오버로드 할 경우 위치 분할을 하지 않는다
- 이 메서드들 사이에는 다른 코드 없이 순차적으로 나타내야함
4. Formatting
- block-like construct는 클래스, 메서드 또는 생성자의 본문을 나타낸다
괄호
- 중괄호는 무조건 필수다
- K&R(Rernighan 및 Ritchie)스타일을 따른다 = Egyption brakets
- 여는 중괄호 앞에 줄 바꿈이 없음
- 여는 중괄호 뒤에 줄 바꿈
- 닫는 중괄호 앞에 줄 바꿈
- 닫는 중괄호 뒤의 줄 바꿈
- 중괄호 뒤에 else 또는 쉼표가 오면 줄바꿈 없음
return () -> {
while (condition()) {
method();
}
};
return new MyClass() {
@Override public void method() {
if (condition()) {
try {
something();
} catch (ProblemException e) {
recover();
}
} else if (otherCondition()) {
somethingElse();
} else {
lastThing();
}
}
};
- 빈 블록의 경우 열린 직후 닫힐 수 있다 void doNothing() {}
블록 들여쓰기
- 블록 들여쓰기의 경우 +2 공백
- 각 문 뒤에는 줄바꿈이 있음 (while, for 등)
열 제한: 100
- 열 제한 100자 -> 유니 코드 코드포인트 기준
- 예외: 열 제한을 따를 수 없거나/ package, import문/ 셀에 복붙되는 ㅈ석의 명령 줄
줄 바꿈
- 줄바꿈은 공식이 없음
- 주요 지침은 더 높은 문법 수준에서 중단
- 기본목표: 명확한 코드 만들기
- lamda의 경우 화살표 옆에서 줄이 안끊어짐 (예외 - 단일식)
MyLambda<String, Long, Object> lambda =
(String label, Long value, Object obj) -> {
...
};
Predicate<String> predicate = str ->
longExpressionInvolving(str);
- 연속 줄을 최소 +4 공백 들여쓰기
공백
- 하나의 빈 줄(세로공백)은 이럴 때 나타남
- 연속적인 멤버 or 클래스의 초기화 (필드, 생성자, 메소드, 중첩클래스, 정적 초기화, 인스턴스 초기화)
- 예외는 두 개의 연속 된 필드 사이에 다른 코드가 없는 빈 줄은 선택사항이다(필드의 논리적 그룹을 위함)
- enum상수 사이 빈 줄은 뒤 참고
- 첫 번째 멤버 or 이니셜라이저 앞이나 클래스 마지막 멤버or이니셜라이저 뒤의 빈 줄 가능은 함
- 여러 개의 연속 된 빈 줄 가능은 함
int a;
int b;
//허용
int c;
int d;
- 띄어쓰기(가로공백)는 다음 위치에만 나타남(리터럴, 주석 및 Javadoc을 제외)
- 예약어 나누는 경우 해당 줄에서 뒤에오는 여는 괄호
- 예약어 나누는 경우 else 또는 catch 이후 나오는 닫는 중괄호에서 분리
- 여는 중괄호 앞
- 예외1: @SomeAnnotation({a,b}) //공백 사용x
- 예외2: String[][] x = {{"foo"}}; //공백 필요x
- 이항 또는 삼항 연산자 양쪽 -> 다음과 같은 "operator-like" 기호에도 적용된다
- 인접한 Type 바인딩의 앰퍼샌드 //<T extends Foo & Bar>
- 여러 예외 처리 catch블록 파이프 //catch (FooException | BarException e)
- for(int a : list)문의 콜론
- 람다식 화살표 //(String str) -> str.length()
- 허용되지 않음1: 메소드 참조의 두 콜론 //Object::toString
- 허용되지 않음2: 다음과 같이 쓰인 점 구분 기호 //object.toString()
- 캐스트 뒤 , :: 또는 )
- // 주석을 시작하는 이중 슬래시 양쪽 -> 여러 개 공백 허용
- 선언의 type과 변수 사이
- 배열 선언문 괄호 안에 공백 :new int[] {5,6} new int[] { 5,6 }
- type annotation과 [] 또는 ...
- 수평 정렬: 허용됨, 하지만 비추
- 특정 토큰이 이전 줄 다른 특정 토큰 바로 아래에 표시되도록 코드에 다양한 수의 추가공백 넣는 방법
- 가독성에 도움은 될 수 있지만 향후 유지관리에 문제됨 (포맷을 위한 한 줄 변경이 폭발 변경을 갖습니다/ 무의미한 작업 초래/ 버전 기록 정보를 손상시키고/ 검토자의 속도를 늦추고/ 병합충돌을 악화시킴)
이렇게까지 말하는데 웬만하면 쓰지말자
private int x; // OK
private Color color; // OK
private int x; // OK, but future edits
private Color color; // 맞춰지지 않은 상태로 둘 수 있음
- 그룹화 괄호: 권장됨
- 코드가 잘못 해석 될 가능성이 없음
- 코드를 읽기 어렵다고 동의하는 경우만 생략된다
- 자바 연산자 우선순위 테이블 매번 볼거임? 그러지 말고 우선순위가 명확해도 괄호로 감싸자!
Enum 클래스
- enum 상수 뒤에 오는 각 쉼표 뒤 줄바꿈은 선택사항, 추가 빈 줄도 허용
- 메서드와 주석 없으면 맨 아래처럼도 가능
- 클래스 형식 지정에 대한 다른 모든 규칙이 적용됨 (중괄호 필수 등)
private enum Answer {
YES {
@Override public String toString() {
return "yes";
}
},
NO,
MAYBE
}
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
변수선언
- 선언 당 하나의 변수
- int a, b; 이런거 쓰지 말자~
- 예외: for 루프 헤더는 여러 변수 선언이 허용됨
- 필요할 때 선언
- 지역변수는 포함 블록or블록 유사 구조 시작부분에서 습관적으로 선언 되지 않음
- 대신 지역변수 범위 최소화 -> 처음 사용되는 지점에 가깝게 선언
- 지역 변수 선언 -> 일반적으로 이니셜라이저 있거나 선언 직후 초기화됨
int count = 0;
for (int i=0; i<10; i++) {
count++;
}
int count2 = 0;
for (int i=0; i<10; i++) {
count2++;
}
배열
- 배열 이니셜 라이저 = block-like
- 모든 배열 이니셜 라이저는 block-like construct 인 것 처럼 형식화될 수 있다
- 다음 경우 모두 가능
int arr[] = new int[] {
0,1,2,3
};
int arr2[] = new int[] {
0,
1,
2,
3
};
int arr3[] = new int[] {
0, 1,
2, 3
};
int arr4[] = new int[]
{0, 1, 2, 3};
(나는 1이랑 2를 많이 쓸 것 같다)
- C스타일 배열 선언 x
- 대괄호는 변수가 아니라 type이라고 생각합시다! //String[] args
Switch문
- 스위치 블록의 중괄호 안에는 하나 이상의 구문 그룹이 있음
- 각 구문 그룹: switch 라벨(case FOO:)과 하나 이상의 명령문으로 구성
- 스위치 블록 내용은 +2 들여쓰기
- Fall-through: 주석
- Switch블록 내 각 구문 그룹 중 하나가 돌연 종료되거나, 다음 구문으로 넘어가게 적을 수 있음
- 이 경우 //fall through 라는 주석을 달 수 있음 (스위치 블록 마지막 명령문에는 필요x)
- 아래 예시 case1 뒤에는 주석 필요x
- default 코드가 없어도 default문 그룹이 포함됨
- enum 유형에 대한 switch문에서 가능한 모든 경우를 포함하는 명시적 케이스를 처리한 경우 -> 명령문 그룹 생략가능
- (IDE나 기타 정적 분석 도구는 누락 된 사례가 있는 경우 경고발행 가능)
switch (input) {
case 1:
case 2:
prepareOneOrTwo();
// fall through
case 3:
handleOneTwoOrThree();
break;
default:
handleLargeNumber(input);
}
Annotations
- 클래스, 메서드 생성자에 적용되는 어노테이션은 document block 바로 뒤에 나타남
- 한 줄에 하나의 어노테이션(각 어노테이션은 자체줄에 나열)
- 파라미터 없는 단일 어노테이션은 한 줄 맨 처음 가능 //@Override public int method() {}
- 필드에 적용되는 어노테이션도 document block 바로 뒤에 나타나지만, 이 경우 여러 개가 같은 줄 나열 가능
- 파라미터, 지역 변수 또는 타입에 대한 형식화 특정 규칙은 없음
@Override
@Nullable
public String getNameIfPresent() { ... }
@Override public int hashCode() {}
@Partial @Mock DataLoader loader;
주석
- 줄 바꿈 앞에는 임의의 공백과 구현 주석이 올 수 ㅇ -> 이 경우행을 공백이 아닌 것으로 렌더링함
- 블록 주석 스타일
- 주변 코드와 동일한 수준에서 들여쓰기 됨
- /* ... */주석의 경우 후속 줄은 이전 줄에 *정렬 된 것으로 시작해야함
/*
* This is
* okay.
*/
// And so
// is this.
/* Or you can
* even do this. */
- 주석은 별포 또는 기타 문자로 그려진 박스에 포함되지 않음
Modifiers(접근 제한자)
- 클래서 및 멤버 Modifiers는 java언어 사양에서 권장하는 순서로 나타냄
public abstract default static final transient volatile synchronized native strictfp
protected ...
private ...
숫자 리터럴
- long값을 갖는 정수 리터럴 L은 대문자 쓰기 //300000000L
- 1(일) 이랑 혼돈을 피하기 위함
5. Naming
모든 식별자 공통규칙
- ASCII 문자 + 숫자 + _
- 각 유효한 식별자 이름은 \w+정규식과 일치
- 특수 접두사 or 접미사 사용 ㄴㄴ
식별자 유형별 규칙
- 패키지 이름
- 모두 소문자
- 연속된 단어는 단순연결, 밑줄 없음, camelCase x
com.example.deepspacenot //o
com.example.deepSpace //x
com.example.deep_space //x
- 클래스이름
- UpperCamelCase
- 명사 or 명사 구
- 인터페이스 이름은 형용사 or 형용사 구 가능 //Readable
- 어노테이션 이름은 특정 규칙 x
- 테스트 클래스 이름의 경우 끝에 Test 붙이기 //HashTest
- 메서드 이름
- lowerCamelCase
- 동사 or 동사 구
- JUnit 테스트 메서드 이름에 밑줄 표시되어 이름의 논리적 구성 요소 구분가능, 각 구성요소는 lowerCamelCase
- <methodUnderTest>_<state>pop_emptyStack
- 테스트 방법의 이름 정하는 규칙 x
- Constant 이름
- 상수 이름은 CONSTANT_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"};
- 상수가 아닌 필드 이름 (정적 or 기타): lowerCamelCase
- 명사 or 명사 구
- 파라미터 이름: lowerCamelCase
- 공용 메소드에서 한 문자 파라미터 이름 피하기
- 지역 변수 이름: lowerCamelCase
- final 이나 불변인 경우에도 지역 변수는 상수로 간주되지 않음 -> 상수 스타일 x
- Type 변수 이름
- 두 가지 스타일 중 하나
- (1) 단일 대문자, 혹은 단일 숫자가 따라올 수 ㅇ //E, T2
- (2) 클래스에 사용되는 형식의 이름 + 대문자 T //RequestT
카멜 케이스 만들기
- 두문자어 또는 IPv6 또는 IOS와 같은 비정상적인 구조가 있는 경우 영어 구를 camelcase로 변환하는 합리적인 방법
- 예측 가능성을 높이기 위해 Google스타일은 다음과 같은 (거의) 결정론적 체계를 지정함
- 산문 형식 이름으로 시작
- 구를 일단 ASCII로 변환, ' 제거 //Muller's book -> Mullers book
- 이 결과를 단어로 나누고 공백과 나머지 구두점(하이픈)으로 분리함
- 모든 것을 lowerCase로 만들고 다음의 첫 번째 문자만 대문자로 표시
- 모든 단어를 단일 식별자로 결합 (원래 단어의 대소문자 거의 무시됨)
- checkNoneempty 가능함
6. 프로그래밍 실습
@Override: 항상 사용
- 부모 메서드가 @Deprecated인 경우 @Override 생략 가능
Caught exception: 무시되지 않음
- Caught excaptions에 응답으로 아무것도 하지 않는 거 드뭄
- catch블록에서 아무 조치 취하지 않을 경우 -> 꼭 주석으로 설명
try {
int i = Integer.parseInt(response);
return handleNumericResponse(i);
} catch (NumberFormatException ok) {
// it's not numeric; that's fine, just continue
}
return handleTextResponse(response);
- 테스트에서 포착된 예외는 excepted이름이거나 이걸로 시작하면 주석없이 무시가능
try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {
}
Static member: 클래스를 사용하여 정규화
- 정적 클래스 멤버에 대한 참조가 정규화되어야하는 경우 해당 클래스의 type의 참조 또는 식이 아닌 해당 클래스의 이름으로 정규화됨
- static 멤버 사용시 클래스 이름으로 쓰자. 변수명 xxxx
Foo aFoo = ...;
Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad
somethingThatYieldsAFoo().aStaticMethod(); // very bad
+) Finalizers는 사용되지 않음. 하지마십시오! -> 하지말자!
7. Javadoc
- javadoc은 뭔지 몰라서 정리하진 않았다
- 다음에 공부한 후에 추가할 생각.. 궁금하신 분은 위 링크 참고해주세요
+) 조금 더 조사한 정보
- + 연산자 양쪽에 공백이 와도 된다
원래는 간단하게 보면서 필요한 것만 정리하려고 했지만, 코드를 막 짰던 지난 코딩세월이 생각나서 반성하는 겸 상세히 적었다.
한 번 쭉 정리해보니 얼추 느낌을 이해했다! 여러 번 반복해서 읽어보고 많이 사용해보면서 익숙해져야겠다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
[JAVA] StringBuilder 비우기(초기화하기) (1) | 2023.10.28 |
---|---|
[JAVA] split vs StringTokenizer (1) | 2023.10.28 |
[JAVA] StringBuffer와 StringBuilder의 차이점 (0) | 2023.10.11 |
[JAVA] Disjoint Set과 Union-Find 알고리즘(백준 1717 집합의 표현) (3) | 2023.10.10 |
[JAVA] 최소힙, 최대힙 사용법 정리(PriorityQueue) (0) | 2023.10.06 |