필드 이름을 _code로 바꿨다. 이제 Badge 클래스 밖에서는 못 읽겠지 싶었는데, 같은 파일의 BadgeLabel이 멀쩡히 읽는다. 반대로 Badge를 다른 파일에서 import해 읽으려니 분석기가 오류를 낸다. private가 상황에 따라 마음을 바꾼 걸까?
경계가 클래스가 아니어서 그렇다. Dart에서 이름 앞의 _는 그 이름을 선언한 라이브러리 밖에서 감춘다. 같은 파일의 다른 클래스, import한 파일을 하나씩 비교해 보자.
Dart 언더스코어 private 범위는 클래스일까?
아니다. Dart에서 라이브러리는 코드의 공개 범위를 나누는 단위다. 보통 Dart 파일 하나가 라이브러리 하나이고, 이름이 _로 시작하면 그 라이브러리 안에서만 보인다. Dart의 라이브러리·가시성 설명은 _의 범위를 클래스가 아닌 라이브러리로 명시한다.
즉 Badge 안에서만 _code가 보인다고 생각하면 다음 예제가 이상해진다. 같은 파일의 다른 클래스도 같은 라이브러리에 속한다.
같은 파일의 다른 클래스는 비공개 필드를 어떻게 읽을까?
badge.dart에 두 클래스를 둔다. BadgeLabel은 Badge의 메서드도 하위 클래스도 아니지만 _code를 읽어 마지막 두 글자만 반환한다.
// badge.dart
class Badge {
Badge(this._code);
final String _code;
}
class BadgeLabel {
static String suffix(Badge badge) =>
badge._code.substring(badge._code.length - 2);
}BadgeLabel이 접근할 수 있는 이유는 특별한 권한을 받은 클래스라서가 아니다. 두 클래스가 같은 라이브러리 안에 있기 때문이다. _를 Java의 클래스 전용 private와 똑같이 읽으면 여기서 헷갈린다.
import한 파일에서 비공개 필드는 왜 오류가 날까?
옆에 main.dart를 만들고 badge.dart를 import한다.
// main.dart
import 'badge.dart';
void main() {
final badge = Badge('A42');
print(BadgeLabel.suffix(badge)); // 42
// print(badge._code); // 이 줄을 활성화하면 분석 오류
}dart run main.dart는 42를 출력한다. 하지만 마지막 줄의 주석을 풀고 dart analyze main.dart를 실행하면 _code를 찾을 수 없다는 undefined_getter 오류가 난다. import로 공개된 Badge와 BadgeLabel은 사용할 수 있지만, 다른 라이브러리의 _code를 직접 읽는 일은 허용되지 않는다.
BadgeLabel.suffix()가 값을 일부 보여 주는 것은 모순이 아니다. 라이브러리 안에서 _code를 읽은 뒤, 공개 메서드가 선택한 결과 42를 바깥으로 돌려준 것이다. 무엇을 공개할지는 메서드를 만든 쪽의 책임이다.
파일을 나눠도 같은 라이브러리일 수 있을까?
보통의 import는 다른 라이브러리를 가져온다. 반면 part로 포함한 파일은 원래 파일과 하나의 라이브러리를 이룬다. 따라서 파일 수만 세어 _의 접근 범위를 판단하면 틀릴 수 있다.
| 코드 위치 | Badge._code 직접 접근 |
|---|---|
badge.dart 안의 다른 클래스 | 가능 |
badge.dart를 import한 main.dart | 불가 |
badge.dart의 part로 포함된 파일 | 가능 |
실제로 위 두 클래스를 본 파일과 part 파일로 나눠 실행해도 접미사 42가 나왔다. 앱 구조를 볼 때는 '같은 폴더인가?'보다 '같은 라이브러리인가?'를 먼저 확인하자. 새 코드를 굳이 part로 나누라는 뜻은 아니다.
비공개 필드라면 비밀값도 안전할까?
_는 Dart 코드의 접근 범위를 정하는 장치이지 암호화나 비밀 보관 기능은 아니다. 이 예제도 공개 메서드가 42를 의도적으로 내보낸다. 비밀 토큰을 앱 코드에 넣어 두고 _token이라고 이름 붙이는 것만으로 숨겨지지는 않는다.
캡슐화가 목적이라면 외부에서 필요한 동작만 공개하고, 민감한 값 자체는 클라이언트에 넣지 않는 편이 안전하다. BadgeLabel.suffix()처럼 필요한 두 글자만 내보낼지, 아예 공개 메서드를 두지 않을지는 그 값을 누가 써야 하는지에 따라 정한다.
핵심 요약
Dart의 _는 클래스가 아닌 라이브러리 경계에서 이름을 감춘다. 그래서 같은 파일의 다른 클래스는 _code를 읽지만, import한 파일에서는 직접 읽지 못한다. part 파일은 같은 라이브러리에 속할 수 있다. 접근 범위와 실제 비밀 보관은 다른 문제이므로, _만 보고 데이터가 안전하다고 판단하지 말자.

