Home final 키워드를 default로 사용하는 것은 좋은 방법일까?
Post
Cancel

final 키워드를 default로 사용하는 것은 좋은 방법일까?

글을 작성하게 된 계기


회사에서 팀원 중 한 분이 왜 final 키워드를 기본으로 선언하는지 물으셨고, 이를 설명하는 과정에서 생각을 한 번 더 정리하기 위해 글을 작성하게 되었습니다.





1. final 키워드에 대한 팀원의 질문


변수를 재 할당을 하지 않는 모든 곳에 final 키워드를 사용 하는 것이 팀 코드 컨벤션 입니다. 다음과 같이요. 그런데 팀원께서 final 키워드를 사용하지 않아도 똑같이 동작 하는데, 왜 이렇게 final 키워드를 사용하는지 물어보셨습니다.

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class ExampleService implements ExampleServiceInterface {
    
    @Override
    public void exampleMethod(
        final String strValue,
        final int intValue,
        final ExampleCommand command
    ) {
        ......
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
public class ExampleEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    public ExampleEntity(
        final Long id,
        final String name
    ) {
        this.id = id;
        this.name = name;
    }
}




사실 팀원 분의 의견도 맞습니다. final 키워드를 사용하지 않아도 똑같이 동작 하며, 여기에 익숙하지 않은 분은 오히려 불편 할 수 있죠. 그래서 이전에 final 키워드를 사용해야 하는 이유에 대해 나름 생각을 정리한 적이 있었는데, 이에 대해 설명드리게 되었습니다.

결론적으로 팀원들이 납득 하셨고, final 키워드 사용이 코드 컨벤션으로 채택됐습니다. 🙇







2. final 키워드를 사용하는 이유


final 키워드를 사용하는 이유는 다양한데요, 크게 개발자의 의도 명시, 예상치 못한 재할당 방지, 불변 객체 설계의 첫걸음 정도라고 생각합니다.

  1. 개발자의 의도 명시
  2. 예상치 못한 재할당 방지
  3. 불변 객체 설계의 첫걸음



2-1. 개발자의 의도 명시

final은 이 변수는 재할당될 일이 없다는 것을 명시적으로 보여줍니다. 협업자(혹은 미래의 나)가 코드를 볼 때, 변수가 변경되지 않는다는 것을 확신할 수 있습니다. 일종의 자기 문서화( self-documenting) 역할을 하는 것이죠.

1
2
// 이후 코드에서 name이 바뀌지 않는다는 사실을 확신할 수 있음
final String name = "jun";

코드리뷰 시, 변수나 파라미터에 final이 붙어 있으면 재할당 되지 않는다는 것을 판단할 수 있어 편해집니다.



2-2. 예상치 못한 재할당 방지

실수로 변수 값을 변경하는 것을 컴파일 타임 에 방지할 수 있습니다. 복잡한 로직에서 특정 값을 고정시켜두는 것이 버그를 줄이는 데 매우 중요하죠. 개인적으로 컴파일 시점에 에러를 알 수 있는 것은 축복이라고 생각하는데요, 배포 후 버그를 수정하려면 너무 골치아픕니다. 🤔

1
2
3
4
// 컴파일 에러 발생: 의도치 않은 재할당 방지
public void calculate(final int base) {
    base = base + 10; 
}

최근 팀원 한 분이 복잡한 로직에서 변수 재 할당으로 꽤 고생을 한 적이 있어 깊은 공감을 하셨… 👀



물롬 참조 값 으로 할당된 객체의 내부 상태는 변경될 수 있기 때문에 이에 대해서는 조심해야겠죠?

1
2
3
4
5
6
7
8
9
public class Main {
    public static void main(String[] args) {
        final List<String> names = new ArrayList<>();

        // 내부 상태 변경 (가능)
        names.add("Alice");
        names.add("Bob");
    }
}



2-3. 불변 객체 설계의 첫걸음

final을 사용하는 습관은 불변성(Immutability) 과도 잘 맞습니다. 불변 객체는 특히 멀티 쓰레드 환경에서 동기화 문제 없이 안전하게 공유될 수 있습니다. 또한 값의 상태가 고정되어 있으므로 디버깅이 쉬워지고, 버그 발생 가능성도 줄어들죠. 이건 너무 많이 들어본 이야기라 생략하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
public class Email {
    private final String value;

    public Email(final String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}







3. final 키워드와 관련된 철학 및 의견, 코드 품질 도구의 설정들


final 키워드에 대한 의견은 다양합니다. final 키워드에 대한 자바 측 의견, final 키워드에 대한 커뮤니티의 의견, 품질 도구의 설정들 을 살펴보겠습니다.

  1. final 키워드에 대한 자바 측 의견
  2. final 키워드에 대한 커뮤니티의 의견
  3. 품질 도구의 설정들




3-1. final 키워드에 대한 자바 측 의견

자바 문서를 읽다보면 간접적으로 final 키워드 사용에 대한 언급들이 나오는데요, 이를 살펴보겠습니다. 아래는 최근 17 버전에 등장한 레코드 클래스(Record Class) 에 대한 내용 입니다. final 키워드의 사용을 강제함으로써, 레코드는 객체 생성 이후 상태 변경을 원천 차단합니다. 간결한 문법과 투명성, 불변성에 대해 언급하고 있네요.

Enhance the Java programming language with records. Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.



Java Tutorials의 Writing Final Classes and Methods에는 final 키워드는 생성자에서 호출되는 메서드가 하위 클래스에 의해 오버라이딩되어 예기치 않은 동작이 발생하는 것을 방지하며, 클래스 전체에 사용하면 상속 자체를 차단해 객체의 일관성과 안정성을 보장한다고 되어 있습니다. 메서드를 넘어 클래스까지 언급하고 있죠.

Methods called from constructors should generally be declared final. If a constructor calls a non-final method, a subclass may redefine that method with surprising or undesirable results. Note that you can also declare an entire class final. A class that is declared final cannot be subclassed.



JSL 17.5에는 버그 방지와 코드 이해를 돕는 문서적 효과에 대해 언급하고 있습니다.

Declaring a variable final can serve as useful documentation that its value will not change and can help avoid programming errors.



또한 final 필드를 사용해 불변성을 지니면 재정의나 상태 변경 가능성이 없으며, 이를 통해 최적화가 가능하다고도 되어 있네요. 멀티쓰레드 환경에서도 예측 가능한 동작을 가능하게 한다고도 돼 있죠?

In particular, compilers have a great deal of freedom to move reads of final fields across synchronization barriers and calls to arbitrary or unknown methods. Correspondingly, compilers are allowed to keep the value of a final field cached in a register and not reload it from memory in situations where a non-final field would have to be reloaded.



직접적으로 final 필드의 장점에 대해 언급한 부분도 있습니다.

final fields also allow programmers to implement thread-safe immutable objects without synchronization.



또한 A Strategy for Defining Immutable Objects 에는 객체 설계 전략에 대해 정리해두었는데요, 모든 필드를 private으로 둔 후, final 키워드 시용을 권장하고 있습니다.

Make all fields final and private.



자바 언어 사양이나 공식 문서에서는 final 키워드의 사용을 강제 하거나 일방적으로 권장 하지는 않습니다. 하지만, 다음과 같은 특정한 상황에서 유용하다고 명확히 기술하고 있습니다. 이는 언어를 제공하는 측은 어느정도 중립 을 지켜야 하기 때문이지 않나 싶습니다.

  • record 클래스는 모든 필드를 암묵적으로 final로 선언하여, 자바에서 구조적으로 불변 객체를 지원하는 예시입니다.
  • 메서드에 final을 사용하면 서브클래스에서 재정의할 수 없도록 하여, 객체의 상태가 일관성을 유지하도록 설계할 수 있습니다.
  • 클래스에 final을 붙이면 상속을 방지할 수 있어, 불필요한 확장을 막고 설계 의도를 분명히 전달할 수 있습니다.
  • 멀티 쓰레드 환경에서는 불변 객체가 안전하게 공유될 수 있기 때문에, final은 동시성 문제를 줄이는 데 기여합니다.



3-2. final 키워드에 대한 커뮤니티의 의견

final 키워드에 대한 커뮤니티의 의견은 다양한데요, 관심 있으신 분은 아래 링크를 한 번 참조해보세요. 대체로 긍정적인 의견이 많습니다.



아래 의견에 가장 동의 하는데요, 명시적으로 해당 변수가 변경되지 않는다는 것을 전달 하며, 개발자가 코드를 작성하기 전, 한 번 더 생각할 틈을 준다 는 내용 입니다. 일단 변수가 재할당 되면 머리가 아픈데, 이런 상황을 방지할 수 있죠. 또한 코드 작성 전, 한 번 더 생각하다보니 마음에 안 드는 코드를 작성할 확률도 적어지고요.

The programmer that wrote this code knew as he was writing it that the values for the final variables should never be changed after assignment, and so made them final. Any attempt to assign a new value to a final variable after assignment will result in a compiler error.



긍정적인 의견만 볼 수는 없는데요, 부정적인 의견도 살펴보겠습니다. 아래는 변수를 final로 선언하면 다른 사람이 값을 바꾸지 못하게 막을 수 있지만, 디버깅할 때 자신이 직접 값을 바꾸기 어려워져 오히려 불편하다는 의견입니다. 결과적으로 final은 코드만 복잡하게 만들 뿐 도움이 안 된다는 주장입니다.

The only reason to declare a variable as final is to keep other programmers from modifying its value. And I don’t like to do that, because often times, while debugging, that other programmer that needs to change a variable’s value is me, and I cannot do that if the variable is final. It does nothing but make my code harder to work with. It’s just clutter.



코드가 지저분해진다는 이유로 final 키워드를 사용하지 않는다는 의견도 있습니다.

The “Java way” is inherently, intrinsically cluttery. I say it’s a good practice, but not one I follow. Tests generally ensure I’m doing what I intend, and it’s too cluttery for my aesthetics.



이 외에도 final 키워드 사용에 대한 부정적 의견은 다양합니다. 찬찬히 읽어보면서 자신의 의견을 정리해보는 것도 좋을 것 같습니다.

  • 디버깅과 테스트 중 유연성 저하
  • 가독성 저하 및 코드 복잡도 증가
  • 모든 변수에 final을 붙이면 오히려 시각적 노이즈만 늘어난다. 꼭 필요한 곳에만 써야 한다.

IDE가 이미 추적 가능한 정보



3-3. 코드 품질 도구의 설정들

정적 코드 분석 도구들에 final 키워드를 사용하지 않으면 경고가 발생 하도록 설정을 할 수 있습니다. CheckStyle의 FinalLocalVariable 과 PMD의 LocalVariableCouldBeFinal 같이요. 코드 품질 도구에 이런 설정이 있다는 것은 final 키워드 사용이 나쁘지만은 않다는 것을 의미하겠죠?







4. 정리


이전에 final 키워드를 사용해야 하는 이유에 대해 나름 정리한 적이 있었는데요, 바쁘다는 핑계로 포스팅을 하진 못했습니다. 참 감사(?)하게도 팀원이 이를 물어보셔서 정리할 수 있는 기회가 생겼네요. 요즘 사람들을 설득하기 위해서는 근거가 필요할 때가 많은데, 이런 생각을 평소에도 하고, 좀 정리해야 겠다는 생각이 듭니다. 추가로 final 키워드에 대한 성능 최적화에 대한 말도 있는데요, 이는 다음 포스팅에서. 언젠간.


This post is licensed under CC BY 4.0 by the author.

MySQL에서 특정 칼럼이 어느 테이블에 있는지 어떻게 찾을 수 있을까?

MySQL의 NULL 비교