티스토리 뷰

반응형

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

익셉션(exception)을 무시하지 마라
코드를 짜다 보면 때로는 아래의 예처럼 익셉션을 완전히 무시하고 싶어질 때가 있습니다.

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

이 함수를 호출할 모듈이 명확하고 그 모듈에서 넘겨주는 value 값에 문제가 없을거라는 확신을 가질 수 있습니다.
하지만 절대 이렇게 코드를 짜서는 안됩니다. 여러분의 코드가 절대로 에러를 일으키지 않을 거라고, 또는 발생할 수 있는 에러를 처리하는 것이 중요하지 않다고 생각할 수도 있으나 위처럼 익셉션을 무시하는 것은 언젠가 여러분의 코드를 다룰 누군가에게 지뢰를 깔아주는 것과 다름없습니다.

"누구든 비어 있는 catch 구문을 만들 때는 뭔가 섬뜩한 기분이 들어야 한다. 물론 그렇게 하는 것이 옳은 때가 분명히 있지만, 적어도 다시 한 번 생각해볼 필요가 있다. 자바를 짤 때는 그 섬뜩한 기분을 무시해선 안 된다."     - 제임스 고슬링 -

따라서 코드에서 발생할 수 있는 모든 익셉션을 정해진 규칙에 따라 처리해야 합니다. 처리 방법은 각각의 경우마다 다릅니다.

허용할 만한 대안들은 아래와 같습니다. 목록의 위에 있을수록 선호도가 높은 방법입니다.

1. 익셉션을 해당 메소드의 caller에 발생시킵니다.
void setServerPort(String value) throws NumberFormatException {
    serverPort = Integer.parseInt(value);
}

2. 추상(abstraction) 정도에 맞는 새로운 익셉션을 발생시킵니다.
void setServerPort(String value) throws ConfigurationException {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) {
    throw new ConfigurationException("Port " + value + " is not valid.");
    }
}

3. 캐치 블록 안에서 적당한 값으로 대체한다.
/** 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
    }
}

4. 익셉션을 캐치한 뒤 새로운 RuntimeException을 발생시킵니다. 위험한 방법이므로 해당 에러가 발생했을 때 올바른 처리 방법이 crash란 확신이 있을 때만 사용해야 합니다.
/** 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);
    }
}

5. 익셉션을 무시하는 것이 올바르다는 확신이 든다면 그렇게 해도 좋습니다. 하지만 합당한 이유를 꼭 코멘트해야 합니다.
/** 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.
    }
 }

exception을 한번에 캐치하지 마라
종종 익셉션을 잡아내는 것에 게을러져 아래 같은 코드를 짜고 싶을 때가 있습니다.
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!
}

모든 익셉션에 대해 익셉션이나 throwable로 한번에 캐치하는 것은 적절하지 못한 방법입니다.
ClassCastException 같은 RuntimeExceptions 등의 전혀 예상하지 못한 익셉션이 애플리케이션 레벨의 에러 핸들링에서야 캐치될 것입니다.
이런 방식은 다양한 에러 상황에 대처하지 못하게 합니다. 누군가 여러분이 작성한 someComplicatedIOFunction 내부에서 새로운 종류의 익셉션을 만들었을 때 컴파일러는 그 새로운 에러를 다른 방식으로 다뤄야 한다는 메시지를 여러분이나 다른 사람에게 전달하지 못합니다.

이 규칙에는 아주 드문 예외도 있습니다. UI에 나타나는 것을 방지한다든지, 배치 잡(batch job)을 계속 돌려야 한다든지 하는 경우에 모든 종류의 에러를 캐치할 최상위 코드나 테스트 코드가 필요할 때 입니다. 이런 경우엔 일반적인 익셉션이나 쓰로어블을 캐치해서 에러를 처리해도 됩니다. 하지만 이와 같은 처리는 언제나 주의해야 하며 왜 이 상황에 이런 처리가 안전한지 설명하는 코멘트를 꼭 달아야 합니다.

다른 대안들:
1. 하나의 try문 뒤에 각각 다른 여러 개의 캐치 블록을 작성합니다. 보기에 어색할 수는 있지만 한번에 모든 익셉션을 캐치하는 것보다 낫습니다. 이 때 각 캐치 블록 안에 중복 코드를 너무 많이 작성하지 않도록 주의해야 합니다.

2. 조밀하게 에러 처리를 할 수 있도록 더 많은 트라이 블록으로 코드를 리팩토링(refactoring) 합니다.

3. 익셉션을 다시 쓰로우(rethrow)합니다. 많은 경우 해당 단계에서 익셉션을 캐치할 필요가 없기 때문입니다.

컴파일러는 코드에서 발생할 수 있는 런타임 문제를 더 쉽게 해결해주고자 익셉션 캐치에 대해 워닝을 표시해줍니다. 해당 워닝을 무시하지 말고 꼭 처리해야 합니다.

finalizer를 사용하지 말라
파이널라이저는 객체가 가비지 콜렉션될 때 실행될 코드를 미리 짜두는 방법 중 하나입니다.

장점: 특히 외부 리소스를 포함해 여러 리소스 정리를 할 때 편리합니다.

단점: 언제 파이널라이저가 호출될지, 심지어 파이널라이저가 호출이 되는지 안되는지도 정확히 알기가 어렵습니다.

따라서 파이널라이저를 사용하면 안됩니다. 대부분의 경우 파이널라이저가 할 일은 올바른 익셉션 처리를 통해 대체 가능합니다.
꼭 필요하다면 close() 메소드나 그 비슷한 것을 정의하고 정확히 언제 해당 메소드가 호출되어야 하는지를 문서로 작성합니다.

import는 끝까지 명시하라
패키지 foo의 클래스 Bar를 사용하고 싶다면 임포트를 할 때 두 가지 선택지가 있습니다.

1.import foo.*;
장점: 임포트문의 수를 줄일 수 있습니다.

2.import foo.Bar;
장점: 정확히 어떤클래스가 사용되는지 알 수 있습니다. 유지보수에 유리합니다.

모든 안드로이드 프로젝트에서는 두번째 방법을 사용해야합니다. 특별한 예외를 둔다면 자바 표준 라이브러리(java.util.*, java.io.*, etc.)나 유닛 테스트 코드(junit.framework.*) 정도입니다.

사라질 라이브러리를 사용하지 말라
안드로이드의 자바 라이브러리와 툴을 사용할 때에도 지켜야 할 컨벤션이 있습니다.
컨벤션의 핵심적인 사항이 바뀌어 이전의 코드가 더 이상 사용되지 않고 사라질(deprecated) 패턴이나 라이브러리를 사용할때가 있습니다. 그렇게 짜여져 있는 기존 코드를 다룰 때는 현재 유지되고 있는 스타일을 계속 사용하는 것도 괜찮으나 새로운 코드를 짤 때는 절대 사라질 라이브러리를 사용하면 안됩니다.

자바독(Javadoc) 표준 코멘트를 사용하라
모든 파일은 저작권에 대한 기술로 시작해야 합니다. 그 뒤로 패키지와 임포트에 관한 문구가 이어지는데 각 블록은 한 줄을 띄웁니다. 그 아래로 클래스와 인터페이스를 선언합니다. 그 다음 자바독 코멘트로 각 클래스와 인터페이스가 무엇을 하는지를 기술하면 됩니다.

/*
 * Copyright (C) 2013 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 {
    ...
}

여러분이 만든 모든 클래스와 이름으로 봤을 때 기능이 자명하지 않은 퍼블릭 메소드에는 각 클래스와 메소드가 무슨 일을 하는지 적어도 한 줄 이상의 자바독 코멘트를 달아야 합니다. 이 때 각 코멘트는 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) {
    ...
}

코멘트의 내용이단지 "Foo를 설정한다(sets Foo)" 등 자명한 이름의 겟(get)과 셋(set) 메소드에는 자바독 코멘트를 달 필요가 없습니다. 하지만 해당 메소드가 조금 더 복잡한 일, 예를 들어 제약 조건을 강화한다거나 중대한 부작용을 야기할 수 있는 경우엔 꼭 문서화를 해야 합니다. 또한 "Foo"라는 속성이 의미하는 바가 자명하지 않을 경우에도 문서화가 필요하다.

여러분이 작성하는 모든 메소드들에, 그것이 퍼블릭이든 아니든, 자바독을 사용하는 편이 좋습니다. API의 일부가 되는 퍼블릭 메소드의 경우는 당연히 자바독을 필요로 합니다.

안드로이드는 현재 자바독 코멘트를 사용함에 있어 특정한 스타일을 제시하고 있진 않지만 자바독 툴을 위한 문서화How to Write Doc Comments for the Javadoc Tool) 의 지시 사항을 따르는 것이 좋습니다.
 
다음편에서 계속 이어 나가겠습니다.


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