프로그래밍의 세계, 특히 협업과 유지보수가 중요한 현대 소프트웨어 개발에서 '코드 가독성'은 더 이상 선택이 아닌 필수 역량입니다. 잘 작성된 코드는 그 자체로 훌륭한 문서가 되며, 미래의 나 자신과 동료 개발자에게 보내는 친절한 안내서 역할을 합니다. Dart 언어는 이러한 클린 코드 철학을 지원하는 여러 강력하고 간결한 문법을 제공하는데, 그중에서도 가장 일상적으로 마주치면서도 그 깊이를 제대로 알지 못하고 넘어가는 문법이 바로 '밑줄(underscore, `_`)'입니다.
많은 개발자들이 밑줄(`_`)을 단순히 '사용하지 않는 변수'를 표시하는 용도로만 알고 있습니다. 맞습니다, 그것이 가장 기본적인 역할입니다. 하지만 Dart 3.0 이후 패턴 매칭(Pattern Matching)이라는 강력한 기능이 도입되면서 밑줄의 역할과 위상은 완전히 달라졌습니다. 이제 `_`는 단순한 관례를 넘어, 코드의 의도를 명확히 하고, 복잡한 데이터 구조를 우아하게 분해하며, 불필요한 코드를 획기적으로 줄여주는 핵심적인 '와일드카드(wildcard)' 패턴으로 진화했습니다.
이 글에서는 이제껏 당신이 어렴풋이 알고 있던 Dart의 밑줄(`_`)에 대한 모든 것을 A부터 Z까지 파헤쳐 보고자 합니다. 함수의 미사용 매개변수를 처리하는 기초적인 용법부터 시작하여, `try-catch`, 비동기 처리, 클래스 상속과 같은 실전 시나리오에서의 활용법, 그리고 Dart 3.0의 꽃이라 불리는 패턴 매칭과의 화려한 조합까지, 당신의 Dart 코드를 한 단계 더 성숙하고 전문적으로 만들어 줄 밑줄의 모든 비밀을 공개합니다. 만약 당신이 코드 리뷰에서 "이 매개변수는 왜 그냥 두셨나요?"라는 질문을 받아본 적이 있거나, 더 깔끔하고 의도가 명확한 코드를 작성하고 싶다면, 이 글이 완벽한 가이드가 될 것입니다.
1. 밑줄(`_`)의 기본: 왜, 그리고 어떻게 사용하는가?
가장 먼저, 밑줄의 가장 근본적인 역할을 이해해야 합니다. 프로그래밍을 하다 보면 필연적으로 '선언은 되었지만 사용되지는 않는' 변수나 매개변수를 마주하게 됩니다.
1.1. "나는 이 값을 의도적으로 무시한다"는 명시적 선언
예를 들어, 어떤 함수의 시그니처(호출 규약)가 프레임워크나 라이브러리에 의해 이미 정해져 있다고 가정해 봅시다. 해당 함수는 세 개의 인자(id, name, timestamp)를 받도록 설계되었지만, 우리의 로직에서는 오직 'id'만 필요할 수 있습니다.
// 나쁜 예: 사용하지 않는 매개변수를 그대로 둠
void processUserData(String id, String name, int timestamp) {
print('처리할 사용자의 ID: $id');
// name과 timestamp는 이 로직에서 전혀 사용되지 않음.
}
위 코드는 문법적으로는 아무런 문제가 없습니다. 하지만 코드를 읽는 사람 입장에서는 궁금증이 생깁니다. 'name과 timestamp는 왜 사용하지 않았을까? 실수로 빠뜨린 로직이 있는 건 아닐까? 아니면 나중에 사용될 예정인가?' 이처럼 불필요한 인지적 부하(cognitive load)를 유발하고, 코드의 의도를 모호하게 만듭니다.
바로 이때 밑줄(`_`)이 등장합니다. 사용하지 않을 매개변수의 이름을 `_`로 대체함으로써 "나는 이 위치의 값을 전달받는다는 것을 알고 있지만, 의도적으로 사용하지 않겠다"는 강력하고 명확한 신호를 보내는 것입니다.
// 좋은 예: 미사용 매개변수를 _로 명시
void processUserData(String id, _, _) {
print('처리할 사용자의 ID: $id');
}
void main() {
processUserData('user-123', 'Alice', 1672531199);
}
이제 이 코드를 보는 모든 개발자는 `processUserData` 함수가 두 번째와 세 번째 인자를 의도적으로 무시한다는 사실을 즉시 파악할 수 있습니다. 만약 무시할 인자가 여러 개라면, 두 번째 인자는 `_`, 세 번째 인자는 `__`, 네 번째는 `___`와 같이 밑줄을 늘려가며 구분할 수 있습니다. (하지만 이는 과거의 관례이며, Dart 2.12 이후부터 `_`는 특별한 식별자가 되었으므로, 이제는 `_`와 `__`가 완전히 다른 변수 이름으로 취급됩니다. 최신 Dart에서는 보통 여러 개의 미사용 매개변수도 각각 `_`로 표시하는 경우가 많지만, 패턴 매칭이 아닌 일반 함수 매개변수에서는 동일한 이름을 사용할 수 없으므로 `_`, `__`를 사용하는 것이 유효합니다.)
핵심: `_`는 '실수로 누락된 것이 아닌, 의도된 무시'임을 나타내는 소통의 도구입니다.
1.2. 익명 함수와 콜백에서의 빛나는 활용성
밑줄의 진가는 특히 리스트(List)나 스트림(Stream) 등을 다룰 때 사용하는 익명 함수(anonymous function) 또는 콜백(callback) 함수에서 드러납니다.
예를 들어, 리스트의 각 요소에 대해 동일한 작업을 반복하는 `forEach` 메소드를 생각해 봅시다. `forEach`는 각 요소(item)를 인자로 받는 함수를 요구합니다.
var fruits = ['사과', '바나나', '오렌지'];
// 각 과일의 이름을 출력하는 경우
fruits.forEach((fruit) {
print('$fruit 한 개를 먹었습니다.');
});
하지만 만약 우리가 리스트의 요소 수만큼 특정 동작을 반복하기만 하면 되고, 요소의 값 자체는 필요 없다면 어떻게 될까요?
var tasks = [/* ... Task 객체들 ... */];
var count = 0;
// 나쁜 예: item 변수를 선언했지만 사용하지 않음
tasks.forEach((item) {
count++;
});
print('총 ${count}개의 작업이 있습니다.');
위 코드의 `item`은 선언만 되고 아무 데도 쓰이지 않습니다. 이는 전형적인 '코드 냄새(code smell)'이며, 바로 `_`를 사용해 개선해야 할 지점입니다.
// 좋은 예: 미사용 콜백 인자를 _로 처리
tasks.forEach((_) {
count++;
});
print('총 ${count}개의 작업이 있습니다.');
코드가 훨씬 간결해지고 의도가 명확해졌습니다. "리스트의 요소가 무엇인지는 중요하지 않고, 단지 요소의 개수만큼 이 블록을 실행하겠다"는 의미가 코드에 그대로 녹아 있습니다.
`Map`의 `forEach`는 `key`와 `value` 두 개의 인자를 받는데, 여기서도 `_`는 유용합니다. 만약 값만 필요하다면 키를, 키만 필요하다면 값을 무시할 수 있습니다.
var userAges = {'Alice': 30, 'Bob': 25, 'Charlie': 35};
// 값(나이)만 필요한 경우
userAges.forEach((_, age) {
if (age >= 30) {
print('$age 살 이상입니다.');
}
});
// 키(이름)만 필요한 경우
userAges.forEach((name, _) {
print('사용자: $name');
});
2. Dart 린터(Linter)와 밑줄: 떼려야 뗄 수 없는 관계
개발자 개개인의 노력만으로 코드 품질을 유지하기는 어렵습니다. 그래서 우리는 정적 분석 도구인 '린터(Linter)'를 사용합니다. Dart는 강력한 린트 규칙(lint rules)을 제공하며, 이를 통해 잠재적인 오류를 찾아내고 코드 스타일을 일관되게 유지할 수 있습니다.
`_`의 사용은 바로 이 린터와 깊은 관련이 있습니다. Dart 린터에는 `avoid_unused_parameters`와 `unused_local_variable`라는 규칙이 있습니다. 이 규칙들은 말 그대로 '사용되지 않는 매개변수'나 '사용되지 않는 지역 변수'가 코드에 존재할 경우 경고를 표시합니다.
// analysis_options.yaml 파일에 린트 규칙이 활성화되어 있다고 가정
// 린터 경고 발생: 'name' is not used. (Try removing the parameter, or naming it '_' or '__'.)
void printId(String id, String name) {
print(id);
}
IDE나 코드 편집기는 위 코드의 `name`에 노란색 또는 파란색 밑줄을 그으며 경고를 보낼 것입니다. 이 경고는 "이봐, `name`이라는 변수를 만들어놓고 왜 안 쓰는 거야? 혹시 버그 아니야?"라고 우리에게 알려주는 것과 같습니다.
이때 `_`를 사용하면 린터에게 "응, 알고 있어. 이건 의도된 거야. 더 이상 경고하지 않아도 돼."라고 말해주는 효과가 있습니다.
// 린터 경고 없음
void printId(String id, _) {
print(id);
}
따라서 `_`의 사용은 단순히 코드를 예쁘게 만드는 장식적인 요소가 아닙니다. 그것은 정적 분석 도구와 소통하여 코드의 정확성에 대한 확신을 높이는 매우 실용적인 행위입니다. 팀 프로젝트에서 린트 규칙을 강제할 경우, `_`를 올바르게 사용하는 것은 선택이 아닌 필수가 됩니다.
3. 실전! 고급 활용 테크닉: 당신의 코드를 한 단계 위로
이제 기본기를 다졌으니, 실제 개발 현장에서 `_`가 어떻게 활용되어 코드의 품격을 높이는지 다양한 시나리오를 통해 살펴보겠습니다.
3.1. `try-catch` 예외 처리: 불필요한 오류 객체 무시하기
`try-catch` 구문은 예외(Exception)가 발생했을 때 프로그램을 비정상적으로 종료시키지 않고 적절히 처리하기 위해 사용됩니다. `catch` 블록은 보통 예외 객체(exception object)와 스택 트레이스(stack trace) 두 가지를 인자로 받을 수 있습니다.
try {
// 예외가 발생할 수 있는 코드
var result = 10 ~/ 0;
} on IntegerDivisionByZeroException catch (e, s) {
print('오류가 발생했습니다: $e');
print('오류 발생 지점: $s');
// 복구 로직...
}
하지만 때로는 어떤 종류의 예외가 발생했는지, 혹은 어디서 발생했는지에 대한 정보가 전혀 필요 없을 때가 있습니다. 우리는 단지 '특정 종류의 예외가 발생했다는 사실' 자체에만 관심이 있고, 정해진 복구 로직을 수행하고 싶을 뿐입니다. 예를 들어, 네트워크 요청 실패 시 무조건 재시도 팝업을 띄우는 경우, 구체적인 `SocketException`의 내용은 사용자에게 보여줄 필요가 없습니다.
try {
await dio.get('https://some-unreliable-api.com');
} on DioError catch (_, __) { // 오류 객체(e)와 스택 트레이스(s) 모두 무시
showRetryPopup();
}
위 코드에서 `catch (e, s)` 대신 `catch (_, __)`를 사용함으로써, "나는 `DioError`가 발생했다는 사실만으로 충분하며, 오류의 상세 내용이나 스택 트레이스는 필요 없다"는 의도를 명확히 드러냈습니다. 만약 오류 객체는 로그로 남기고 싶지만 스택 트레이스는 필요 없다면 `catch (e, _)`와 같이 필요한 부분만 남기고 나머지를 무시할 수도 있습니다.
3.2. 비동기 프로그래밍 (`Future`와 `Stream`): 미래의 결과 값은 필요 없을 때
Dart의 비동기 처리는 `Future`를 통해 이루어집니다. `Future`는 미래의 어느 시점에 완료될 작업을 나타내며, 작업이 완료되면 결과 값을 반환합니다. 우리는 `.then()` 콜백을 사용하여 이 결과 값을 받아 처리할 수 있습니다.
Future<String> fetchUserName() {
// 1초 후 'Alice'를 반환하는 비동기 작업
return Future.delayed(Duration(seconds: 1), () => 'Alice');
}
void main() {
fetchUserName().then((name) {
print('가져온 사용자 이름: $name');
});
}
그런데, 만약 `Future`가 완료되었다는 '사실'만이 중요하고, 그 결과 값 자체는 중요하지 않은 상황이라면 어떨까요? 예를 들어, 서버에 데이터를 저장하는 `saveData()` 함수가 성공적으로 완료되면 `true`를 반환한다고 합시다. 우리는 단지 저장이 완료된 후에 "저장 완료!"라는 스낵바를 보여주고 싶을 뿐, `true`라는 값 자체에는 관심이 없습니다.
Future<bool> saveData() {
// ... 데이터를 저장하는 로직 ...
return Future.value(true);
}
// saveData()가 완료되면 스낵바를 보여줌
saveData().then((isSuccess) { // isSuccess 변수가 사용되지 않음
showSnackbar('저장 완료!');
});
// _ 를 사용하여 개선
saveData().then((_) {
showSnackbar('저장 완료!');
});
`(_) { ... }` 구문을 통해 우리는 "비동기 작업의 결과 값은 무엇이든 상관없으니, 작업이 완료되기만 하면 이 코드를 실행해줘"라는 명확한 지시를 내리고 있습니다. 이는 코드를 더 간결하게 만들 뿐만 아니라, 로직의 핵심에 더 집중할 수 있게 해줍니다.
3.3. 클래스 상속과 메소드 오버라이딩: 부모의 약속, 자식의 자유
객체 지향 프로그래밍에서 상속은 코드 재사용성을 높이는 중요한 개념입니다. 자식 클래스는 부모 클래스의 메소드를 오버라이드(override, 재정의)하여 자신만의 동작을 구현할 수 있습니다. 이때, 부모 클래스의 메소드 시그니처를 반드시 따라야 합니다.
예를 들어, 다양한 알림 리스너를 위한 추상 클래스 `NotificationListener`가 있다고 상상해 봅시다.
abstract class NotificationListener {
// 알림 수신 시 호출되는 메소드. 알림 내용(payload)과 전송 시간(timestamp)을 받음
void onReceived(String payload, int timestamp);
}
이제, 우리는 '단순히 알림이 왔다는 사실을 카운트'하기만 하는 `SimpleNotificationCounter`를 구현하려고 합니다. 이 클래스는 알림의 내용(`payload`)이나 시간(`timestamp`)에는 전혀 관심이 없습니다.
class SimpleNotificationCounter implements NotificationListener {
int count = 0;
@override
// 나쁜 예: payload와 timestamp를 선언했지만 사용하지 않아 린터 경고 발생
void onReceived(String payload, int timestamp) {
count++;
print('새 알림 도착! 총 $count 개');
}
}
이 경우에도 `_`는 완벽한 해결책입니다. 부모 클래스와의 약속(메소드 시그니처)은 지키면서, 현재 구현에 불필요한 인자들은 과감히 무시할 수 있습니다.
class SimpleNotificationCounter implements NotificationListener {
int count = 0;
@override
// 좋은 예: 부모의 시그니처를 존중하면서 미사용 인자를 깔끔하게 처리
void onReceived(_, _) {
count++;
print('새 알림 도착! 총 $count 개');
}
}
3.4. Flutter 개발자를 위한 꿀팁: `BuildContext`와 위젯 빌더
Flutter 개발자라면 `BuildContext`를 지겹도록 마주쳤을 것입니다. 또한 `Builder` 위젯이나 다양한 `...Builder` 형태의 위젯(예: `StreamBuilder`, `FutureBuilder`)을 사용하여 UI를 동적으로 구성하는 일도 흔합니다. 이러한 빌더 함수들은 대부분 `BuildContext`와 함께 다른 값(스냅샷, 값 등)을 인자로 받습니다.
때로는 위젯 트리를 구성할 때 `BuildContext`가 전혀 필요 없는 정적인 위젯을 반환하는 경우가 있습니다.
// Builder 위젯은 context를 인자로 받는 builder 함수를 요구한다.
Builder(
// 나쁜 예: context를 선언했지만 사용하지 않는다.
builder: (context) {
return const Text('이 텍스트는 컨텍스트 정보가 필요 없어요.');
},
)
// 좋은 예: 미사용 context를 _ 로 처리한다.
Builder(
builder: (_) {
return const Text('이 텍스트는 컨텍스트 정보가 필요 없어요.');
},
)
`showModalBottomSheet`와 같은 함수에서도 빌더 패턴이 사용됩니다. 이 빌더 역시 `BuildContext`를 받지만, 단순히 고정된 UI를 보여주는 경우에는 이 컨텍스트가 필요 없을 수 있습니다.
showModalBottomSheet(
context: context,
builder: (_) => const MyFixedContentWidget(), // 빌더의 context는 무시
);
이처럼 Flutter 코드 곳곳에서 `_`를 적절히 활용하면, 불필요한 변수 선언을 줄이고 코드의 핵심 로직을 더욱 돋보이게 만들 수 있습니다.
4. Dart 3.0 혁명: 패턴 매칭과 밑줄(`_`)의 화려한 만남
지금까지의 내용만으로도 `_`의 유용성은 충분하지만, Dart 3.0에 도입된 패턴 매칭은 `_`를 단순한 '미사용 매개변수'에서 '강력한 와일드카드 패턴'으로 격상시켰습니다. 이제 `_`는 데이터의 구조와 값을 검사하고 분해하는 과정에서 "이 자리에는 어떤 값이 와도 상관없다"는 의미의 와일드카드 역할을 수행합니다.
4.1. 리스트와 맵 구조 분해 (Destructuring)
패턴 매칭을 이용하면 리스트나 맵의 값을 여러 변수에 한 번에 할당할 수 있습니다. 이를 구조 분해(destructuring)라고 합니다. 이때 `_`를 사용하면 원하지 않는 요소를 건너뛸 수 있습니다.
var point = [10, 20, 5]; // x, y, z 좌표
// 첫 번째와 세 번째 값만 필요하고, 두 번째 값은 무시하고 싶을 때
var [x, _, z] = point;
print('x: $x, z: $z'); // 출력: x: 10, z: 5
맵(Map)에서도 마찬가지입니다. 특정 키-값 쌍만 추출하고 나머지는 무시할 수 있습니다.
var userJson = {
'name': 'Alice',
'age': 30,
'email': 'alice@example.com'
};
// name과 email만 필요하고 age는 필요 없을 때
var {'name': name, 'email': email, 'age': _} = userJson;
print('Name: $name, Email: $email');
4.2. `if-case`와 `switch` 표현식에서의 와일드카드
`if-case` 구문은 변수가 특정 패턴과 일치하는지 검사할 때 사용됩니다. `_`는 이 과정에서 "값은 상관없이 구조만 맞으면 돼"라는 조건을 표현하는 데 탁월합니다.
List<Object> data = [1, 'hello', 2.5];
// data의 첫 번째 요소가 정수(int)이기만 하면 참, 값은 상관없음
if (data case [int _, ...]) {
print('리스트의 첫 요소는 정수입니다.');
}
// data가 정확히 세 개의 요소로 이루어져 있고, 두 번째 요소가 문자열'hello'이기만 하면 참
if (data case [_, 'hello', _]) {
print('리스트의 두 번째 요소는 "hello"입니다.');
}
`switch`가 표현식(expression)으로 확장되면서 패턴 매칭의 활용도는 극에 달합니다. `_`는 `switch` 문에서 `default`와 유사한 '그 외 모든 경우(catch-all)'를 처리하는 역할을 합니다.
String checkStatus(Object status) {
return switch (status) {
// status가 튜플 (int, 'success') 형태일 때
(int code, 'success') => '성공 코드: $code',
// status가 튜플 (int, 'error') 형태일 때
(int code, 'error') => '에러 코드: $code',
// status가 문자열 'pending'일 때
'pending' => '대기 중...',
// 그 외 모든 경우를 _ 로 처리
_ => '알 수 없는 상태'
};
}
print(checkStatus((200, 'success'))); // 출력: 성공 코드: 200
print(checkStatus('unknown')); // 출력: 알 수 없는 상태
4.3. 레코드(Record) 타입에서 특정 필드 무시하기
Dart 3.0에 함께 도입된 레코드(Record)는 여러 값을 담는 익명의 불변 복합 타입입니다. 패턴 매칭을 통해 레코드의 필드를 손쉽게 분해할 수 있으며, 이때도 `_`가 활약합니다.
(String, int) fetchUser() {
return ('Bob', 42); // 이름과 나이를 반환
}
void main() {
// 반환된 레코드에서 이름만 필요하고 나이는 무시
var (name, _) = fetchUser();
print('사용자 이름은 $name 입니다.'); // 출력: 사용자 이름은 Bob 입니다.
}
5. 주의하세요! 밑줄(`_`) 사용 시 흔히 저지르는 실수와 함정
이처럼 강력하고 편리한 `_`이지만, 몇 가지 주의할 점이 있습니다. 이를 제대로 이해하지 못하면 오히려 코드에 혼란을 주거나 예상치 못한 오류를 만날 수 있습니다.
5.1. 함정 1: `_`는 '일반 변수 이름'이 아니다
오래된 버전의 Dart에서는 `_`가 다른 이름처럼 일반적인 변수 이름으로 사용될 수 있었습니다. 그래서 `var _ = 10; print(_);`와 같은 코드도 가능했습니다. 하지만 Dart가 발전하면서 `_`는 점차 특별한 의미를 갖게 되었고, 이제 `_`는 값을 읽어올 수 없는, 즉 할당은 가능하지만 참조할 수 없는 특수한 식별자로 취급됩니다.
var _ = '이 값은 무시됩니다.';
// print(_); // 컴파일 오류: The name '_' isn't a getter.
이것은 매우 중요한 변화입니다. `_`에 어떤 값을 할당하더라도 그 값은 즉시 버려진다고 생각하면 쉽습니다. `_`는 저장 공간이 아니라 '블랙홀'과 같습니다.
만약 밑줄로 시작하는 변수 이름이 필요하다면(예: 라이브러리 내부에서 사용하는 private 변수처럼), `_myVariable`과 같이 `_` 뒤에 다른 문자를 붙여서 사용해야 합니다.
5.2. 함정 2: 하나의 매개변수 목록에 `_` 중복 사용 불가
일반적인 함수를 선언할 때, 매개변수 목록 안에서 `_`라는 이름을 두 번 이상 사용할 수 없습니다. 모든 매개변수는 고유한 이름을 가져야 하기 때문입니다.
// 컴파일 오류: The name '_' is already defined.
void myFunction(int _, String _) {
// ...
}
이런 경우에는 `_`와 `__`를 사용하여 각기 다른 이름으로 만들어주어야 합니다.
// 정상 동작
void myFunction(int _, String __) {
// ...
}
흥미롭게도, 앞서 살펴본 패턴 매칭에서는 `_`가 여러 번 등장해도 괜찮습니다.
if (myList case [_, _, int _]) { // OK!
// ...
}
그 이유는 컨텍스트에 따라 `_`의 역할이 다르기 때문입니다. 함수 매개변수 선언에서 `_`는 (비록 무시되더라도) '이름'으로 취급되어 고유해야 합니다. 반면 패턴 매칭에서 `_`는 변수 이름이 아니라 '어떤 값이든 일치하는 와일드카드 패턴' 그 자체이기 때문에 여러 번 사용해도 충돌하지 않습니다. 이 미묘한 차이를 이해하는 것이 중요합니다.
5.3. 함정 3: 모든 것을 `_`로 바꾸는 습관의 위험성
`_`의 편리함에 익숙해지다 보면, 사용하지 않는 모든 변수를 기계적으로 `_`로 바꾸려는 유혹에 빠질 수 있습니다. 하지만 이는 때로 코드의 가독성을 해치거나 잠재적인 버그를 숨기는 원인이 될 수 있습니다.
매개변수가 현재는 사용되지 않지만, 함수의 역할이나 맥락상 중요한 의미를 담고 있거나, 가까운 미래에 사용될 가능성이 높다면 이름을 그대로 두는 것이 더 나을 수 있습니다. 린터가 경고를 보내더라도, 그 경고는 "이봐, 이 중요한 값을 아직 사용하지 않았어. 잊은 거 아니야?"라는 유용한 알림이 될 수 있습니다.
// 좋지 않은 예: 맥락이 사라짐
// 이 함수는 'event'를 받아 처리하는 리스너임이 분명한데,
// _로 바꾸면 어떤 종류의 이벤트를 무시하는지 알 수 없다.
void onUserLeaveEvent(_) {
_cleanup();
}
// 더 나은 예: 의도가 명확함
// 현재는 userEvent 객체를 사용하지 않지만, 함수의 역할을 명확히 알려준다.
// 나중에 로그를 추가하거나 특정 사용자 정보를 확인해야 할 때 수정하기도 용이하다.
void onUserLeaveEvent(UserEvent userEvent) {
_cleanup();
}
`_`는 '정말로, 완전히, 앞으로도 영원히 관심 없는 값'에 사용하는 것이 가장 좋습니다. 무분별한 사용은 오히려 코드의 자가 문서화(self-documenting) 능력을 떨어뜨릴 수 있음을 기억해야 합니다.
결론: 클린 코드를 향한 작은 발걸음, 밑줄(`_`)
단순한 문장부호처럼 보였던 Dart의 밑줄(`_`)이 얼마나 깊은 의미와 강력한 기능을 담고 있는지 함께 살펴보았습니다. 미사용 매개변수를 처리하는 기본적인 역할을 넘어, 린터와의 소통, 비동기 및 예외 처리의 우아함, 그리고 Dart 3.0 패턴 매칭의 핵심 와일드카드에 이르기까지, `_`는 현대 Dart 개발에서 빼놓을 수 없는 필수 요소입니다.
밑줄(`_`)을 올바르게 사용하는 것은 단순히 코드를 몇 글자 줄이는 행위가 아닙니다. 그것은 코드의 의도를 명확히 하고, 동료 개발자 및 정적 분석 도구와 효과적으로 소통하며, 미래의 유지보수 비용을 줄이는 '클린 코드'를 향한 작지만 매우 중요한 발걸음입니다.
이제 당신의 Dart 코드 속에서 목적 없이 방치된 변수나 매개변수가 보인다면, 주저하지 말고 `_`를 사용하여 명확한 의도를 불어넣어 주세요. 이 작은 변화가 모여 당신의 프로젝트 전체를 더욱 견고하고 우아하게 만들어 줄 것입니다.
0 개의 댓글:
Post a Comment