목 차
1. 소개
2. 소스파일 기본
3. 소소파일 구조
4. 포멧팅
A. 중괄호
B. Line-wrapping
C. 공백처리
D. 기타
5. 네이밍
A. Type 명에 대한 규칙
B. Camel Case 란?
6. 프로그래밍 관례
A. @Override 는 필수
B. 예외처리
C. Static 멤버 접근
D. Finalizers
7. javadoc
1. 소개
우선 원문은 다음 링크에서 확인할 수 있다. 네이버와 구글 양쪽에서 검색해봤는데 적어도 5페이지 내에는 정리된 내용이 없는 것 같아서 내가 정리하기로 했다. 원문을 그대로 번역하지는 않을 예정이고 최대한 원래 의도를 해치지 않는 선에서 쉽게 풀어서 쓸 예정이다.
코딩 스타일이라는 것이 반드시 따라야 하는 룰은 아니지만 프로젝트 초기에 한번은 이야기해야 하는 부분이기도 하다. 구글에서는 어떤 코딩 스타일을 따르고 있는지 이번 기회에 살펴보는 것도 나쁘지는 않을 것 같다.
2. 소스파일 기본
파일이름 : 클래스 이름과 동일하게 대소문자 구별해서 작성한다. 확장자는 당연히 java이다.
예) DigitalCamera Class가 구현된 소스파일의 이름 ⇒ DigitalCamera.java
소스파일의 인코딩은 UTF-8로 통일한다.
공백문자는 ‘스페이스키’ 만 허용한다.(ASCII 0x20) 탭은 사용하지 않는다.
코드 내에서 특수 문자를 표현할 때 ‘\b’, ‘\n’, ‘\\’을 사용한다.
8진법(\012), 유니코드 표현 등을 사용하지 않는다.
코드의 가독성을 높일 수 있다면 유니코드를 사용해도 무방하다.
3. 소소파일 구조
소스코드는 위 형태로 진행되어야 하며 각 섹션 사이에는 공백 라인이 하나 들어가야 한다. 아래는 안드로이이드 프로젝트에서 가져온 소스 코드이다. 필요한 부분 외에는 다 제거 했으니까 당연히 동작하지 않는다.
/* * Copyright (C) 2008 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.contacts.activities; import android.app.ActionBar; import android.app.ActionBar.LayoutParams; import android.widget.SearchView.OnQueryTextListener; import com.android.contacts.ContactsUtils; import com.android.contacts.R; import com.android.contacts.calllog.CallLogFragment; /** * The dialer activity that has one tab with the virtual 12key * dialer, a tab with recent calls in it, a tab with the contacts and * a tab with the favorite. This is the container and the tabs are * embedded using intents. * The dialer tab's title is 'phone', a more common name (see strings.xml). */ public class DialtactsActivity { private static final String TAG = "DialtactsActivity"; public static final boolean DEBUG = false; /** Used to open Call Setting */ private static final String PHONE_PACKAGE = "com.android.phone"; private static final String CALL_SETTINGS_CLASS_NAME = "com.android.phone.CallFeaturesSetting"; }
Package 문의 경우 아무리 길어도 한 문장으로 써야 한다.
import 문에는 *을 쓰지 않는다. Package 문과 동일하게 한 문장에 작성한다.
import 문은 그룹핑을 해서 순서에 맞춰 작성한다. 다른 그룹간에는 공백라인을 한 줄 추가한다.(예제 참조) 그룹 이름 및 순서는 다음과 같다. (static import → google 내부 package → google 외부 package → java → javax)
한 java 파일에는 하나의 class 만 존재해야 한다. (nested 클래스가 아니다)
클래스 멤버의 순서는 절대적인 것이 없다. 다만 이들의 순서가 논리적이여야 한다. 가령 새로운 메소드가 추가되었다고 해서 클래스의 가장 마지막에 구현되는 것은 논리적이지 않다.
멤버 순서에 대해서 하나의 제약 사항이 있는데 동일한 메소드명 (생성자들, 오버라이딩된 메소드들) 은 한 곳에 모아두어야 한다는 것.
4. 포멧팅
A. 중괄호
K&R style을 따르며 제거가 가능해도 무조건 쓴다. 가령 아래는 틀린 방식이다.
if (total > MAX_SUM) return true; else return false;
위의 틀린 예는 다음과 같이 바꿔 쓸 수 있다.
if (total > MAX_SUM) { return true; } else { return false; }
아래는 중괄호를 제대로 사용한 예제이다.
return new MyClass() { @Override public void method() { if (condition()) { try { something(); } catch (ProblemException e) { recover(); } } } };
여는 중괄호를 뒤에는 코드가 없어야 한다.
닫는 중괄호 앞에는 다른 코드가 없어야 한다.
문장에 닫는 중괄호만 있는 케이스는 함수가 끝나거나 제어문이 끝날 때이다.
그래서 조건문의 경우 마지막에만 닫는 중괄호 단독으로 쓰일 수 있다.
if (fragment instanceof DialpadFragment) { mDialpadFragment = (DialpadFragment) fragment; } else if (fragment instanceof CallLogFragment) { mCallLogFragment = (CallLogFragment) fragment; } else if (fragment instanceof PhoneFavoriteFragment) { mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment; } else if (fragment instanceof PhoneNumberPickerFragment) { mSearchFragment = (PhoneNumberPickerFragment) fragment; }
하지만 코드가 없는 메소드의 경우는 그냥 닫아도 괜찮다. (더 간결하니까)
void doNothing() {}
B. Line-wrapping
코드의 길이가 페이지 넓이를 넘어갈 때, 하나의 문장을 두 문장 이상으로 나눠서 표현하는데 이런 행위를 Line-wrapping이라고 한다. 사실 절대적인 법칙은 없고 상황에 따라서 적절하게 사용하면 된다.
가끔은 Line-wrapping을 시도하기 전에 리팩토링을 실시하는 것이 더 올바른 선택일 수 있다.
public void onAttachFragment(Fragment fragment) { ... 중략 … final FragmentTransaction transaction = getFragmentManager().beginTransaction(); ... 중략 … }
위와 같이 과도한 들여쓰기로 발생하는 코드의 경우 멤버 변수화 시킴으로써 해결할 수 도 있다.
final FragmentTransaction mTransaction = getFragmentManager().beginTransaction(); public void onAttachFragment(Fragment fragment) { ... 중략 … }
Line-wrapping 된 문장은(다음 줄로 내려온 라인) 기본적으로 2번 이상의 들여쓰기를 해야 한다. (4개의 스페이스 이상) 그리고 여러 문장이 연속해서 내려올 경우 첫 번째 내려온 문장과 동일한 들여쓰기를 유지한다. 아래 예제 참조.
어디서 다음 문장으로 내릴까?
문장을 대입 연산자가 아닌 곳에서 잘라야 할 경우 심볼 앞에서 내린다.
this.someString = new StringBuffer() .append("Humans ") .append("are ") .append("intelligent ") .append("apes."); this.someString = "Humans " + "are " + "intelligent " + "Apes.";
대입 연산자에서 잘라야 할 경우 대입 연산자 뒤에서 문장을 내린다.
private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener = new OnPhoneNumberPickerActionListener() {
함수호출의 경우 ‘(‘는 첫 문장에 두고 나머지를 다음 문장으로 내린다.
addContactOptionMenuItem.setIntent( new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
콤마 (‘,’)의 경우, 앞의 식별자와 동일한 단어로 취급한다.
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldTop, int oldRight, int oldBottom) {
C. 공백처리
공백라인
아래와 같은 상황에서 공백라인이 들어간다. 여러 줄의 공백라인은 추천하지 않는다.
클래스 멤버들을 구별하는 데 사용 : 메소드, 생성자, 멤버변수
멤버변수의 경우, 사이에 코드가 없다면 굳이 공백라인을 넣지 않아도 된다.
메소드 내부에서 논리적으로 그룹핑 되는 부분
공백문자
Listlist if (true) { // ... } float var = a + i * (4 / Math.pow(0.5, x)) - 45.0f; if (someBoolean) { // ... } else { // ... } for (int i = 0; i < 10; i++) { // } catch (FooException | BarException e)
if, for, catch 와 그 다음에 오는 ‘(‘ 사이에 공백문자
else, catch와 그 이전에 오는 ‘}’ 사이에 공백문자
‘,’, ‘:’, ‘;’ 다음 이나 타입 캐스트시의 ‘)’ 다음에 공백문자
연산자 앞 뒤로는 공백문자 삽입
연산자와 비슷한 심볼에서도 앞 뒤로 공백문자 삽입
변수정렬
변수명을 보기 좋게 하기 위해서 정렬을 이용할 때가 있는데 이는 추천하지 않는다고 한다.
private int x; // this is fine private Color color; // this too private int x; // permitted, but future edits private Color color; // may leave it unaligned
이유를 살펴보니 정렬을 쓰지 않는 것이 좋을 것 같다. 변수정렬을 할 경우, 유지보수 문제를 일으킨다고 하는데 구구절절 옳은 이야기들 뿐이다.
한 문장만 수정하고 싶은데 정렬 때문에 여러 문장을 수정해야 하는 상황 발생
코드리뷰를 힘들게 하며 가독성을 위해 너무 많은 잠재적 시간을 투입해야 함
코드 수정 이력에 필요 없는 정보를 넣게 되고 잠재적으로 충돌 가능성이 높아짐
D. 기타
Switch 문
switch (input) { case 1: case 2: prepareOneOrTwo(); // fall through - 이런식으로 주석을 달아달라고 한다. case 3: handleOneTwoOrThree(); break; default: handleLargeNumber(input); }
switch 문 내에서도 들여쓰기는 적용해야 한다.
아무 처리가 없더라도 default는 무조건 있어야 한다.
주석 스타일
/* * This is // And so /* Or you can * okay. // is this. * even do this. */ */
Modifier 순서
public protected private abstract static final transient volatile synchronized native strictfp
그 외
들여쓰기는 스페이스키 2개로 정의한다.
한 문장에는 하나의 statement 만을 쓴다.
한 문장에는 하나의 변수 만을 선언한다.
변수 선언은 함수 처음에 하지 않는다. 최대한 변수가 사용되는 위치 근처에서 선언하여 변수의 스코프를 최소화 시킨다.
한 라인의 문자는 80개 혹은 100개만 쓴다. 그 보다 길 경우 다음 라인으로 내린다.
5. 네이밍
모든 식별자들은 ASCII와 숫자 값만 사용해야 한다. 식별자에 prefix나 suffixes는 사용하지 않는다. 즉, 아래와 같은 형태의 식별자는 사용되지 않는다.
name_
mName
s_name
kName
A. Type 명에 대한 규칙
package 명
모두 소문자로 기술한다. 단어가 달라지더라도 무조건 소문자를 사용한다.
com.example.deepspace (O)
com.example.deepSpace (X)
com.example.deep_space (X)
Class 명
UpperCarmelCase를 사용한다. 간단히 설명해서 대문자로 시작하고 단어가 바뀔 때마다 다시 대문자로 표시하는 거다. (AccountManagerInfo) CarmelCase에 대한 내용은 아래 따로 설명되어 있으니 참조하자. Class 명은 당연히 명사 혹은 명사구이다. (예외도 있음, Readable, Runnable)
테스트 클래스의 경우 마지막에 Test로 끝나도록 한다. (HashIntegrationTest)
Method 명
lowerCarmelCase를 사용한다. 간단히 소문자로 시작해서 단어가 바뀔 때마다 다시 대문자로 표시하는 방식이다. (sendMessage) 메소드명의 경우 동사이거나 동사구다.
테스트 클래스 내의 테스트 메소드의 경우 test<테스트할 메소드>_<상태> 이런 식으로 작성한다. (예: testPop_emptyStack) 물론 절대적인 방식은 아니다.
상수명
CONTANT_CASE 방식을 사용한다. 모두 대문자를 사용하며 단어 사이에 밑줄을 표시한다. 당연히 명사나 명사구여야 한다. 아래는 상수 선언의 예.
// Constants static final int NUMBER = 5; static final ImmutableListNAMES = ImmutableList.of("Ed", "Ann"); 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 mutableCollection = new HashSet (); static final Logger logger = Logger.getLogger(MyClass.getName()); static final String[] nonEmptyArray = {"these", "can", "change"};
멤버변수명 / 인자명 / 로컬변수명
lowerCarmerCase를 사용한다. 메소드명과 다른 점은 동사가 아닌 명사라는 점. 한문자는 피하자.
B. Camel Case 란?
우선 ASCII 문자로 모두 바꾼다. [‘ “ `] 등의 문자를 제거한다.
단어로 구별해서 단어 사이에 스페이스를 하나 둔다.
각 단어의 첫 문자 외에는 모두 소문자로 바꾼다.
단어들을 모두 합친다. (스페이스 제거)
6. 프로그래밍 관례
A. @Override 는 필수
오버라이딩이 된 모든 경우에 @Override는 필수로 기입한다. 부모 클래스의 메소드를 재정의 하거나 인터페이스를 구현했을 때도 마찬가지다. 다만 부모 쪽에서 @Deprecated를 선언했을 경우에는 자식 쪽에서 @Override를 생략할 수 있다.
B. 예외처리
모든 예외는 무시하지 말고 처리한다. 만약 예외를 처리하지 않을 거면 그 이유에 대해서 명확하게 주석을 달자. 당연히 테스트 코드에서는 필요시 무시해도 된다.
try { int i = Integer.parseInt(response); return handleNumericResponse(i); } catch (NumberFormatException ok) { // it's not numeric; that's fine, just continue } return handleTextResponse(response);
C. Static 멤버 접근
반드시 클래스 명으로 접근하자.
Foo aFoo = new Foo();
Foo.aStaticMethod(); // 좋은 예
aFoo.aStaticMethod(); // 나쁜 예 : 객체로 접근하고 있다.
somethingThatYieldsAFoo().aStaticMethod(); // 아주 나쁜 예
D. Finalizers
사용하지 않는다. Effective Java Item 7을 참조하라고 한다. 그래서 참조해봤다.
우선 finalizer에는 아래와 같은 단점이 있다고 한다.
언제 실행될 지 알 수 없다. 일반적으로 가비지 콜랙터에 의해서 호출이 되기 때문에 임의의 시점에 실행된다. 따라서 실행 시간이 중요한 코드는 finalizer에 두지 않는다.
finalizer의 실행 주기는 전적으로 JVM 구현에 달려있어서 JVM 마다 다른 양상을 띈다.
자바 언어 명세에는 finalizer의 반드시 실행될 것인지도 보장하지 않는다.
finalizer 수행 중에 예외가 발생하면 예외가 무시된다.
엄청난 성능 저하를 일으킨다. 객체 소멸이 약 430배 느려진다고 주장한다.
대안은? 각 인스턴스에서 자신의 종료 여부를 유지 관리해야 한다. 예를 들면 InputStream이나 OutputStrem 같은 클래스들은 close 라는 메소드를 별도로 제공한다.
책에는 몇 개의 예외 상황을 기술하고 있는데 결국은 방어코드 목적들이다. 깔끔하게 사용하지 않는 것이 좋아 보인다. 더 궁금하면 책을 참조하자.
7. javadoc
javadoc block의 기본 형태는 다음과 같다
/** * JavaDoc 테스트 클래스입니다. * * @author Je */ public class JavaDoc { /** * 곱셈을 합니다. * * @param a * @param b * @return int */ public int multiply(int a, int b){ return a * b; } }
/** 다음은 공백이다.
문단과 문단 사이에는 공백라인이 들어가고 @ 시작하기 전에도 공백라인이 들어간다.
@param, @return, @throws, @deprecated 순으로 사용한다. 설명은 무조건 기술해야 하며 한 문장을 넘어가면 4개 이상의 스페이스로 들여쓰기 한다.
최소한 public class의 public 혹은 protected 멤버에는 javadoc을 기술하는 것이 좋다. 그리고 주석의 경우 모두 javadoc 타입으로 다는 것을 추천한다.
'소프트웨어 > 기타' 카테고리의 다른 글
gitlab (git) 저장소 사용하기 (2) | 2015.06.21 |
---|---|
[Eclipse] WebApp 개발하기 (20) | 2015.05.06 |