# 주석과 애노테이션 활용

Dart에서 주석과 애노테이션은 코드의 가독성을 높이고, 메타데이터를 활용하는 중요한 도구로 사용된다. 특히 애노테이션은 리플렉션을 통해 런타임 시점에서 코드에 추가적인 의미를 부여하거나 동작을 변경할 수 있게 해준다. 이 장에서는 주석과 애노테이션의 사용법과 적용 사례를 상세히 다룬다.

#### 주석의 기본 개념

주석은 코드 내에서 설명을 추가하기 위한 텍스트이다. Dart에서는 주석을 다음과 같은 방식으로 작성할 수 있다.

* **한 줄 주석:** `//`로 시작하며, 해당 줄의 나머지 부분이 모두 주석 처리된다.
* **블록 주석:** `/* */` 사이의 모든 내용이 주석으로 처리된다. 여러 줄에 걸친 주석을 작성할 때 유용하다.
* **문서 주석:** `///`로 시작하는 주석으로, 일반적으로 클래스나 함수의 API 문서를 작성할 때 사용된다.

**한 줄 주석**

```dart
// 이 코드는 변수 a에 5를 할당한다.
int a = 5;
```

**블록 주석**

```dart
/*
이 함수는 두 개의 정수를 더한 결과를 반환한다.
매개변수:
- x: 첫 번째 정수
- y: 두 번째 정수
*/
int add(int x, int y) {
  return x + y;
}
```

**문서 주석**

문서 주석은 `dartdoc` 도구를 사용하여 자동으로 API 문서를 생성할 수 있다. 이는 코드에 대한 설명을 표준화된 형식으로 제공하며, 클래스나 함수, 변수에 대한 자세한 정보를 포함할 수 있다.

```dart
/// 두 숫자를 더하는 함수이다.
/// 
/// [x]와 [y]는 더할 두 정수이다.
/// 반환값은 두 숫자의 합이다.
int add(int x, int y) {
  return x + y;
}
```

#### 애노테이션의 기본 개념

애노테이션(Annotation)은 코드에 메타데이터를 추가하는 방법이다. Dart에서는 `@` 기호를 사용하여 애노테이션을 적용하며, 주로 클래스, 함수, 변수 등에 메타데이터를 추가하는 데 사용된다.

애노테이션의 주요 역할은 다음과 같다:

* 코드에 의미 부여
* 특정 런타임 동작을 트리거
* 테스트, 디버깅, 성능 최적화 시 중요한 정보 전달

**기본 애노테이션 사용법**

가장 기본적인 Dart의 애노테이션은 `@override`이다. 이는 상속된 클래스에서 메소드를 재정의할 때 사용된다. 예를 들어, 부모 클래스의 메소드를 자식 클래스에서 재정의할 때, `@override` 애노테이션을 사용하여 의도적으로 재정의한 것임을 나타낸다.

```dart
class Parent {
  void sayHello() {
    print('Hello from Parent');
  }
}

class Child extends Parent {
  @override
  void sayHello() {
    print('Hello from Child');
  }
}
```

위 코드에서 `@override`는 `sayHello` 메소드가 부모 클래스의 동일한 메소드를 재정의했음을 나타낸다. 만약 이 애노테이션을 생략할 경우, Dart 컴파일러는 실수로 인해 재정의가 잘못되었는지 확인할 수 없기 때문에, 이를 명시적으로 사용함으로써 코드의 가독성과 안전성을 높일 수 있다.

#### 사용자 정의 애노테이션

Dart에서는 직접 애노테이션을 정의하여 사용할 수도 있다. 이를 통해 특정 클래스나 메소드에 추가적인 정보를 제공하거나, 리플렉션을 사용하여 런타임에서 동작을 결정할 수 있다. 애노테이션은 일반적으로 클래스 형태로 정의되며, 클래스 이름 앞에 `@`를 붙여 사용한다.

다음은 애노테이션을 정의하고 사용하는 예시이다.

```dart
// 애노테이션 정의
class Todo {
  final String who;
  final String what;
  
  const Todo(this.who, this.what);
}

// 애노테이션 사용
@Todo('John Doe', 'Implement the login feature')
void login() {
  // 로그인 기능 구현
}
```

이 코드에서 `@Todo` 애노테이션은 `login` 함수에 메타데이터를 추가하여 누가 어떤 작업을 해야 하는지 나타낸다. Dart의 `const` 키워드를 사용하여 애노테이션 클래스를 정의하면, 해당 애노테이션은 컴파일 타임에 적용된다.

#### 애노테이션의 활용 사례

애노테이션은 다양한 방식으로 활용될 수 있으며, 주로 다음과 같은 상황에서 사용된다.

* **API 문서화:** 애노테이션을 통해 코드에 추가적인 설명을 제공하여, API 문서화 과정에서 더 명확한 정보를 전달할 수 있다.
* **런타임 동작 제어:** 리플렉션(reflection)을 사용하여 애노테이션이 적용된 코드를 런타임에서 동적으로 확인하고, 그에 따라 동작을 변경할 수 있다.
* **테스트 프레임워크:** 특정 함수나 클래스에 애노테이션을 사용하여 테스트 대상을 지정하거나, 테스트의 종류를 구분하는 데 활용될 수 있다.

**리플렉션을 통한 애노테이션 활용**

리플렉션은 런타임에 코드 구조를 탐색하고 수정할 수 있는 기능이다. Dart에서 리플렉션은 `dart:mirrors` 패키지를 통해 제공되며, 이를 사용하여 애노테이션이 적용된 요소를 확인하거나 동적으로 처리할 수 있다.

다음은 리플렉션을 통해 애노테이션이 적용된 메소드를 확인하는 예시이다.

```dart
import 'dart:mirrors';

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

@Todo('Alice', 'Implement the logout feature')
void logout() {
  print('Logging out...');
}

void main() {
  // 리플렉션을 사용하여 애노테이션 정보 확인
  var mirror = reflect(logout);
  var metadata = mirror.function.metadata;

  for (var annotation in metadata) {
    if (annotation.reflectee is Todo) {
      var todo = annotation.reflectee as Todo;
      print('Todo: ${todo.who}, Task:${todo.what}');
    }
  }
}
```

이 코드에서 `reflect()` 메소드를 사용하여 함수 `logout`에 적용된 애노테이션을 탐색하고, 애노테이션의 값인 `who`와 `what`을 출력한다. 리플렉션을 통해 이러한 애노테이션을 런타임에 동적으로 처리할 수 있다.

#### 애노테이션의 활용 범위

Dart에서 애노테이션은 클래스, 함수, 변수, 매개변수 등 다양한 요소에 적용될 수 있다. 애노테이션의 적용 범위는 코드를 설계하는 데 있어 매우 유연하게 활용될 수 있다. 주석과는 달리, 애노테이션은 실제로 코드 실행에 영향을 미칠 수 있기 때문에, 적절한 상황에서 이를 사용함으로써 코드의 유지보수성과 확장성을 높일 수 있다.

**클래스에 애노테이션 적용**

클래스에 애노테이션을 적용하여, 해당 클래스가 어떤 역할을 수행하는지 메타데이터를 추가할 수 있다.

```dart
@deprecated
class OldClass {
  // 오래된 클래스 로직
}
```

위 코드에서 `@deprecated` 애노테이션은 `OldClass`가 더 이상 사용되지 않는 클래스임을 나타낸다. 이 애노테이션은 IDE나 개발 도구에서 경고 메시지를 출력하여, 개발자에게 해당 클래스의 사용을 피할 것을 권장한다.

**매개변수에 애노테이션 적용**

Dart에서는 함수의 매개변수에도 애노테이션을 적용할 수 있다. 이를 통해 함수의 특정 매개변수가 어떤 역할을 하는지 명확하게 설명하거나, 추가적인 처리를 할 수 있다.

```dart
void printMessage(@required String message) {
  print(message);
}
```

이 코드에서 `@required` 애노테이션은 `message` 매개변수가 필수임을 나타내며, 이 함수가 호출될 때 반드시 해당 매개변수가 전달되어야 함을 명시한다.

**변수에 애노테이션 적용**

변수에도 애노테이션을 추가하여, 해당 변수의 특성을 설명하거나 특정 규칙을 적용할 수 있다.

```dart
class Person {
  @nonVirtual
  final String name;

  Person(this.name);
}
```

이 예시에서 `@nonVirtual` 애노테이션은 `name` 변수가 더 이상 재정의되지 않도록 보호한다.

#### 애노테이션으로 메타데이터 처리

메타데이터는 코드 실행과는 직접적인 관련이 없지만, 코드가 더 명확하게 이해될 수 있도록 돕는 중요한 정보를 제공한다. 애노테이션은 이러한 메타데이터를 코드에 부여하여, 다른 개발자나 도구들이 코드를 해석하는 데 도움이 된다.

#### 사용자 정의 애노테이션의 고급 활용

사용자 정의 애노테이션을 이용하면 특정 코드에 대한 정보를 구조적으로 저장하고 런타임에서 그 정보를 이용하여 특정 동작을 수행할 수 있다. Dart에서 애노테이션은 상수로 정의되기 때문에 컴파일 타임에 해당 애노테이션이 어떻게 사용될지를 결정할 수 있다. 사용자 정의 애노테이션을 통해 런타임에서 동적 분석을 하거나 특정 메타데이터를 참조하여 동작을 제어할 수 있다.

**사용자 정의 애노테이션의 예**

아래 예제는 애노테이션을 사용하여 특정 API 메소드가 어떤 HTTP 요청 메소드에 해당하는지를 명시하는 경우이다.

```dart
class HttpMethod {
  final String method;
  const HttpMethod(this.method);
}

@HttpMethod('GET')
void fetchUser() {
  print('Fetching user data...');
}

@HttpMethod('POST')
void createUser() {
  print('Creating new user...');
}
```

위 예제에서 `@HttpMethod('GET')`와 같은 사용자 정의 애노테이션을 통해 `fetchUser` 함수가 GET 요청에 대응한다는 것을 명시할 수 있다. 이와 같이 API를 개발할 때 애노테이션을 이용하면 각 함수가 담당하는 HTTP 메소드를 명확하게 구분할 수 있다.

**리플렉션을 사용한 API 요청 처리**

위에서 정의한 `HttpMethod` 애노테이션을 기반으로 리플렉션을 통해 API 요청을 처리하는 코드를 작성할 수 있다. 런타임에서 각 메소드에 어떤 HTTP 메소드가 적용되었는지 동적으로 확인하고, 그에 따라 적절한 로직을 수행하게 할 수 있다.

```dart
import 'dart:mirrors';

void handleApiCall(Symbol methodName) {
  var mirror = reflectClass(ApiHandler);
  var method = mirror.declarations[methodName] as MethodMirror;
  
  if (method.metadata.isNotEmpty) {
    for (var meta in method.metadata) {
      if (meta.reflectee is HttpMethod) {
        var httpMethod = meta.reflectee as HttpMethod;
        print('Handling API call with HTTP method: ${httpMethod.method}');
      }
    }
  }
}

class ApiHandler {
  @HttpMethod('GET')
  void getUser() {
    print('User data retrieved');
  }

  @HttpMethod('POST')
  void addUser() {
    print('New user added');
  }
}

void main() {
  handleApiCall(#getUser);  // Handling API call with HTTP method: GET
  handleApiCall(#addUser);  // Handling API call with HTTP method: POST
}
```

위 코드에서 `handleApiCall` 함수는 리플렉션을 통해 `getUser`와 `addUser` 메소드에 적용된 애노테이션을 확인하고, 해당 메소드가 어떤 HTTP 메소드를 처리하는지 동적으로 출력한다. 이처럼 애노테이션을 활용하면 API의 메타데이터를 간결하게 관리하고, 코드의 유지보수성을 높일 수 있다.

#### 애노테이션과 데이터 검증

애노테이션은 코드에서 데이터 검증 규칙을 정의하는 데에도 사용할 수 있다. 예를 들어, Dart에서 폼 입력을 처리할 때 애노테이션을 사용하여 특정 필드가 필수 입력인지, 또는 특정 패턴을 만족해야 하는지 등을 지정할 수 있다.

**데이터 검증 애노테이션 예시**

다음 예시는 데이터 검증을 위한 애노테이션을 정의하고, 이를 사용하는 방법을 보여준다.

```dart
class Required {
  const Required();
}

class Email {
  const Email();
}

class UserForm {
  @Required
  String name;
  
  @Email
  String email;
  
  UserForm(this.name, this.email);
}

void validate(Object form) {
  var mirror = reflect(form);
  
  mirror.type.declarations.forEach((key, declaration) {
    var field = mirror.getField(key);
    for (var meta in declaration.metadata) {
      if (meta.reflectee is Required && field.reflectee == null) {
        print('${MirrorSystem.getName(key)} is required.');
      }
      if (meta.reflectee is Email && !RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(field.reflectee)) {
        print('${MirrorSystem.getName(key)} is not a valid email.');
      }
    }
  });
}

void main() {
  var form = UserForm(null, 'not_an_email');
  validate(form);  // Output: name is required. email is not a valid email.
}
```

이 코드에서 `@Required` 애노테이션은 필수 입력 필드를 나타내며, `@Email` 애노테이션은 이메일 형식의 유효성을 검사하는 역할을 한다. `validate` 함수는 리플렉션을 통해 해당 필드가 적절한 값을 가지고 있는지 확인하고, 규칙을 위반할 경우 오류 메시지를 출력한다.

***

Dart의 주석과 애노테이션은 코드의 가독성을 높이는 동시에 런타임 동작에 중요한 영향을 미칠 수 있는 강력한 도구이다. 주석은 주로 코드의 설명이나 문서화를 위한 용도로 사용되지만, 애노테이션은 메타데이터를 코드에 부여하여 더욱 유연한 구조를 만들 수 있다. 애노테이션은 리플렉션과 결합하여 다양한 런타임 동작을 제어하고, 코드의 확장성과 유지보수성을 높이는 데 중요한 역할을 한다.
