소프트웨어 개발의 세계에서 되돌릴 수 없는 실수는 때로 단 한 줄의 명령어로 발생합니다. 잘못된 디렉토리에서 flutter clean
을 실행해 중요한 빌드 캐시를 날려버리거나, 신중한 검토 없이 flutter pub upgrade
를 실행했다가 수많은 의존성 충돌과 마주하는 경험은 많은 개발자에게 차가운 식은땀을 흘리게 합니다. 이러한 잠재적 재앙을 피하기 위해 Dart와 Flutter CLI(Command Line Interface)는 매우 강력하면서도 종종 간과되는 도구를 제공합니다. 바로 --dry-run
옵션입니다.
--dry-run
은 '마른 실행', 즉 '가상 실행'을 의미합니다. 이 플래그를 명령어에 추가하면, 시스템은 실제 파일 생성, 수정, 삭제와 같은 영구적인 변경을 가하는 대신, 해당 명령어가 실행될 경우 어떤 일이 발생할지를 상세하게 보고합니다. 이것은 단순한 미리보기 기능을 넘어, 개발자가 자신의 의도를 시스템이 정확히 이해했는지 확인하고, 예상치 못한 부작용을 사전에 차단하며, 복잡한 변경 사항을 팀원들과 공유하기 전에 명확한 계획을 세울 수 있게 해주는 핵심적인 안전장치입니다.
이 글에서는 --dry-run
옵션의 철학적 배경부터 시작하여, flutter create
, flutter pub
, 그리고 dart run build_runner
와 같은 주요 명령어와 함께 사용하는 구체적이고 실전적인 방법들을 깊이 있게 탐구할 것입니다. 단순히 '이런 기능이 있다'는 것을 넘어, 이 강력한 도구를 여러분의 일상적인 개발 워크플로우에 통합하여 생산성과 안정성을 극대화하는 방법을 제시하고자 합니다.
--dry-run의 철학: 왜 '미리보기'가 중요한가?
--dry-run
옵션의 존재는 현대 소프트웨어 개발, 특히 복잡한 도구 체인과 의존성 관리가 필수적인 생태계에서 매우 중요한 원칙을 반영합니다. 바로 '예측 가능성(Predictability)'과 '의도 확인(Intent Verification)'입니다.
명령줄 인터페이스는 그래픽 사용자 인터페이스(GUI)와 달리 사용자에게 즉각적인 시각적 피드백을 주지 않는 경우가 많습니다. 엔터 키를 누르는 순간, 수백, 수천 개의 파일이 생성되거나 변경될 수 있으며, 이 과정은 대부분 사용자에게 투명하게 보이지 않습니다. 이러한 환경에서 개발자의 '의도'와 시스템의 '해석' 사이에 불일치가 발생할 위험은 항상 존재합니다.
--dry-run
은 이 간극을 메우는 다리 역할을 합니다. "내가 지금 실행하려는 이 명령어가 정확히 어떤 결과를 가져올 것인가?"라는 근본적인 질문에 대한 답을 제공함으로써, 개발자는 확신을 가지고 다음 단계를 진행할 수 있습니다. 이는 "두 번 측정하고 한 번 잘라라(Measure twice, cut once)"는 오랜 격언을 디지털 세계에 적용한 것과 같습니다.
더 나아가 이 철학은 DevOps 문화에서 강조하는 '멱등성(Idempotency)' 개념과도 맞닿아 있습니다. 멱등성이란 동일한 작업을 여러 번 수행하더라도 시스템의 상태가 항상 동일하게 유지되는 특성을 의미합니다. --dry-run
은 비록 멱등성 자체를 보장하지는 않지만, 명령 실행의 결과를 미리 보여줌으로써 개발자가 비멱등적인(non-idempotent) 위험한 작업을 수행하기 전에 그 영향을 명확히 인지하고 제어할 수 있도록 돕습니다. 이는 CI/CD 파이프라인과 같은 자동화된 환경에서 스크립트의 안정성을 검증하는 데 특히 유용합니다.
flutter create
: 완벽한 프로젝트의 첫 단추
새로운 Flutter 프로젝트를 시작하는 flutter create
명령어는 --dry-run
의 가치를 가장 직관적으로 보여주는 훌륭한 예시입니다. 이 명령어는 단순히 빈 폴더를 만드는 것이 아니라, 선택한 템플릿과 플랫폼에 따라 수십 개의 파일과 디렉토리로 구성된 복잡한 구조를 생성합니다.
기본 사용법과 결과 분석
가장 기본적인 형태로 flutter create
와 함께 --dry-run
을 사용해 보겠습니다.
$ flutter create --dry-run my_awesome_app
이 명령을 실행하면 실제 my_awesome_app
디렉토리는 생성되지 않습니다. 대신, 터미널에는 다음과 유사한 출력이 나타납니다.
Running "flutter create --dry-run my_awesome_app" in /path/to/your/projects...
[✓] Flutter project my_awesome_app would be created in /path/to/your/projects/my_awesome_app
[+ ] my_awesome_app/.gitignore
[+ ] my_awesome_app/.metadata
[+ ] my_awesome_app/analysis_options.yaml
[+ ] my_awesome_app/pubspec.yaml
[+ ] my_awesome_app/README.md
[+ ] my_awesome_app/lib/main.dart
[+ ] my_awesome_app/test/widget_test.dart
[+ ] my_awesome_app/android/build.gradle
... (and many more files) ...
Would create project my_awesome_app.
이 출력은 매우 유용한 정보를 담고 있습니다. 각 줄의 접두사를 통해 어떤 작업이 수행될지 알 수 있습니다.
[✓]
: 작업의 주요 목표 또는 성공적으로 생성될 디렉토리를 나타냅니다.[+ ]
: 새로 생성될 파일을 의미합니다.- (만약 있었다면)
[* ]
: 기존 파일에 내용이 변경되거나 덮어쓰여질 것임을 나타냅니다. - (만약 있었다면)
[-]
: 삭제될 파일이나 디렉토리를 나타냅니다.
이 간단한 미리보기를 통해 프로젝트 이름에 오타가 없는지, 의도한 위치에 생성되는지, 기본적으로 어떤 파일들이 포함되는지를 한눈에 파악할 수 있습니다.
고급 옵션과의 조합
flutter create
의 진정한 힘은 다양한 옵션과 결합될 때 나타나며, 이때 --dry-run
은 더욱 빛을 발합니다. 예를 들어, 특정 조직 이름(organization name)을 사용하고, iOS와 Android 플랫폼만 지원하며, 기본 애플리케이션 템플릿이 아닌 'plugin' 템플릿을 사용하여 프로젝트를 생성하고 싶다고 가정해 봅시다. 이 모든 요구사항을 반영한 명령어는 꽤 길고 복잡해질 수 있습니다.
$ flutter create --dry-run --org com.example.corp --template=plugin --platforms=ios,android -a kotlin -i swift my_super_plugin
이 명령어를 --dry-run
없이 바로 실행했다가 옵션 하나를 잘못 입력했다면, 생성된 디렉토리를 전부 지우고 처음부터 다시 시작해야 하는 번거로움이 발생합니다. 하지만 --dry-run
을 사용하면, 생성될 파일 목록과 구조를 미리 확인할 수 있습니다. 예를 들어, iOS 부분은 Swift로, Android 부분은 Kotlin으로 제대로 설정되었는지, `pubspec.yaml` 파일에 플러그인 관련 설정이 올바르게 포함될 것인지 등을 검토할 수 있습니다. 이를 통해 단 한 번의 실행으로 완벽하게 구성된 프로젝트 뼈대를 만들 수 있는 확신을 얻게 됩니다.
flutter pub
: 의존성 지옥에서 살아남기
Flutter(그리고 Dart) 개발에서 의존성 관리는 프로젝트의 성패를 좌우하는 핵심 요소 중 하나입니다. pubspec.yaml
파일과 flutter pub
명령어는 이 과정을 돕지만, 때로는 복잡한 의존성 트리와 버전 제약 조건으로 인해 예상치 못한 결과를 낳기도 합니다. --dry-run
은 '의존성 지옥(Dependency Hell)'을 예방하는 강력한 방패가 되어줍니다.
flutter pub upgrade --dry-run
: 업데이트의 파급 효과 예측
프로젝트가 오래될수록 flutter pub upgrade
명령어는 두려운 존재가 될 수 있습니다. 이 명령어는 pubspec.yaml
에 명시된 버전 제약(예: ^1.0.4
)을 만족하는 최신 버전으로 모든 패키지를 업데이트하려고 시도합니다. 이 과정에서 하나의 패키지 업데이트가 다른 수십 개의 전이 의존성(transitive dependencies) 업데이트를 유발할 수 있으며, 최악의 경우 주요 변경 사항(breaking changes)이 포함된 메이저 버전 업데이트로 인해 코드가 깨질 수도 있습니다.
이때 flutter pub upgrade --dry-run
이 해결사로 나섭니다.
$ flutter pub upgrade --dry-run
실행 결과는 다음과 같은 형태로 나타납니다.
Resolving dependencies...
http 0.13.5 (1.1.0 available)
provider 6.0.5 (6.1.1 available)
path 1.8.2 (1.8.3 available)
async 2.9.0 (would be 2.11.0)
collection 1.17.0 (would be 1.17.2)
meta 1.8.0 (would be 1.9.1)
These packages are locked to an older version.
To update them, use `flutter pub upgrade`.
1 package is constrained to a satisfiable version.
To update it, edit pubspec.yaml.
_fe_analyzer_shared 58.0.0 (61.0.0 available)
이 출력은 금광과도 같습니다. 각 줄은 중요한 정보를 담고 있습니다.
- `(X available)`: 현재
pubspec.lock
파일에 고정된 버전과,pubspec.yaml
의 제약 조건 내에서 사용 가능한 최신 버전을 보여줍니다. - `(would be Y)`:
flutter pub upgrade
를 실제로 실행했을 때 변경될 버전을 보여줍니다.
개발자는 이 목록을 통해 다음을 파악할 수 있습니다.
- 주요 변경 사항 예측: 만약 `provider`가 `6.0.5`에서 `7.0.0`으로 업데이트될 예정이라면, 이는 잠재적인 주요 변경 사항을 의미합니다. 개발자는 `provider`의 변경 로그(changelog)를 미리 확인하고 코드 수정 계획을 세울 수 있습니다.
- 전이 의존성 확인: 내가 직접 추가하지 않은 `async`나 `collection` 같은 패키지들도 다른 패키지에 의해 업데이트될 것임을 알 수 있습니다. 이는 예상치 못한 사이드 이펙트를 추적하는 데 도움이 됩니다.
- 업데이트 범위 제어: 모든 패키지를 한 번에 업데이트하는 것이 부담스럽다면, 이 목록을 보고 특정 패키지만 `flutter pub upgrade <package_name>` 명령으로 개별 업데이트하는 전략을 세울 수 있습니다.
결론적으로, flutter pub upgrade --dry-run
은 의존성 업데이트를 '깜깜이 상자'에서 투명한 프로세스로 바꾸어 줍니다.
flutter pub add/remove --dry-run
: 의존성 변경의 사전 검증
--dry-run
은 패키지를 추가하거나 제거할 때도 유용합니다. 새로운 패키지를 추가할 때, 해당 패키지가 기존의 다른 의존성들과 호환되는지 미리 확인하는 것은 매우 중요합니다.
$ flutter pub add new_package --dry-run
이 명령은 new_package
를 `pubspec.yaml`에 추가하고 의존성을 해결하는 과정을 시뮬레이션합니다. 만약 `new_package`가 요구하는 `http`의 버전이 현재 프로젝트에 설치된 `http` 버전과 충돌한다면, `flutter pub`은 실제 파일을 변경하기 전에 버전 해결 실패(version solving failed) 메시지를 출력할 것입니다. 이를 통해 개발자는 잠재적인 충돌을 미리 인지하고 다른 패키지를 찾아보거나 버전 제약을 조정하는 등의 조치를 취할 수 있습니다.
마찬가지로, 패키지를 제거할 때도 --dry-run
은 안전장치가 됩니다.
$ flutter pub remove provider --dry-run
만약 `provider` 패키지를 제거했을 때, 다른 패키지가 `provider`에 의존하고 있어서 프로젝트가 깨질 위험이 있다면, 이 가상 실행 과정에서 문제가 드러날 수 있습니다. 이는 의존성 트리의 복잡한 관계 속에서 발생할 수 있는 '나비 효과'를 사전에 방지해 줍니다.
build_runner
: 코드 생성의 투명성 확보
JSON 직렬화(json_serializable
), 불변 객체 생성(freezed
), 의존성 주입(get_it
, injectable
), 상태 관리(riverpod_generator
) 등을 위해 코드 생성(code generation)을 사용하는 프로젝트에서 build_runner
는 필수적인 도구입니다. 하지만 프로젝트가 커질수록 build_runner
의 실행 시간은 길어지고, 때로는 어떤 파일이 생성되고, 수정되고, 삭제되는지 정확히 알기 어려울 수 있습니다.
$ dart run build_runner build --dry-run
이 명령을 사용하면 `build_runner`는 실제 `.g.dart` 파일을 생성하는 대신, 수행할 작업 목록을 출력합니다. 이 목록에는 다음과 같은 정보가 포함될 수 있습니다.
- 생성될 파일:
[INFO] Will generate asset: <package>|lib/src/models/user.g.dart
- 삭제될 파일:
[INFO] Will delete asset: <package>|lib/src/models/old_model.g.dart
- 건너뛸 파일: 소스 파일이 변경되지 않아 다시 생성할 필요가 없는 파일들.
이는 다음과 같은 상황에서 특히 유용합니다.
- 리팩토링 후 검증: 모델 클래스의 이름을 바꾸거나 파일을 다른 디렉토리로 이동했을 때, 기존의 오래된
.g.dart
파일이 올바르게 삭제되고 새로운 위치에 새 파일이 생성될 것인지 확인할 수 있습니다. - `--delete-conflicting-outputs` 옵션과의 결합: 이 옵션은 빌드 과정에서 충돌하는 출력물을 자동으로 삭제해주는 편리한 기능이지만, 잘못 사용하면 필요한 파일을 삭제할 수도 있는 위험한 옵션입니다.
dart run build_runner build --delete-conflicting-outputs --dry-run
조합을 사용하면, 어떤 파일들이 '충돌'로 간주되어 삭제될 것인지를 미리 확인함으로써 치명적인 실수를 예방할 수 있습니다. - 빌드 시간 예측: 대규모 프로젝트에서 전체 빌드를 실행하기 전에
--dry-run
을 통해 변경될 파일의 수를 확인하고, 이를 통해 빌드에 소요될 시간을 대략적으로나마 예측할 수 있습니다. 변경 사항이 없다면 굳이 전체 빌드를 실행할 필요가 없음을 빠르게 판단할 수 있습니다.
시야 넓히기: --dry-run은 Dart/Flutter만의 것이 아니다
--dry-run
또는 이와 유사한 '미리보기' 기능의 개념은 훌륭한 CLI 도구들이 공유하는 보편적인 설계 패턴입니다. 이 개념을 이해하는 것은 Dart/Flutter 생태계를 넘어 여러분의 개발 역량 전반을 향상시키는 데 도움이 됩니다.
- Git:
git clean -n
또는git clean --dry-run
은 버전 관리되지 않는 파일들 중 어떤 것들이 삭제될지를 미리 보여주어, 중요한 파일을 실수로 삭제하는 것을 방지합니다. - rsync: 파일 동기화 도구인
rsync
의-n
또는--dry-run
옵션은 두 디렉토리 간에 어떤 파일이 복사, 수정, 삭제될지를 실제 작업 없이 보여줍니다. 서버 배포 스크립트에서 필수적인 검증 단계입니다. - kubectl: 쿠버네티스 커맨드라인 도구인
kubectl
은kubectl apply -f my-deployment.yaml --dry-run=client
와 같은 옵션을 제공하여, 클러스터에 변경 사항을 적용하기 전에 생성될 리소스의 YAML 정의를 출력해줍니다. - Terraform: 인프라스트럭처를 코드로 관리하는 도구인 Terraform의
terraform plan
명령어는--dry-run
철학의 정수라고 할 수 있습니다. 이 명령어는 현재 인프라 상태와 코드의 차이점을 분석하여, 어떤 리소스가 생성, 수정, 파괴될지를 상세한 계획으로 보여줍니다.
이처럼 다양한 도구들이 '가상 실행' 기능을 제공하는 이유는, 복잡하고 파급 효과가 큰 작업을 다룰 때 '실수 방지'와 '의도 확인'이 얼마나 중요한지를 보여주는 증거입니다. --dry-run
을 능숙하게 사용하는 습관은 특정 기술 스택을 넘어, 신중하고 안정적인 운영 능력을 갖춘 개발자의 상징이 됩니다.
실전 워크플로우: --dry-run을 습관으로 만들기
--dry-run
의 가치를 이해했다면, 다음 단계는 이를 자연스러운 개발 습관으로 만드는 것입니다.
개인 개발 습관
파일 시스템에 변경을 가하거나 의존성을 수정하는 거의 모든 명령어 앞에 --dry-run
을 붙이는 것을 근육 기억으로 만드세요.
- 새 브랜치에서 작업을 시작하고 의존성을 업데이트해야 할 때: 먼저
flutter pub upgrade --dry-run
을 실행하여 변경될 패키지 목록을 확인합니다. - 코드 생성이 필요한 모델을 수정한 후: 바로
dart run build_runner build
를 실행하지 말고,--dry-run
으로 변경 사항을 먼저 확인합니다. - 복잡한
flutter create
명령어를 작성할 때: 최종 명령어를 실행하기 전에 항상--dry-run
으로 옵션이 올바르게 적용되었는지 검토합니다.
이 몇 초의 추가적인 단계가 나중에 발생할 수 있는 몇 시간의 디버깅 시간을 절약해 줄 수 있습니다.
팀 협업에서의 활용
--dry-run
의 출력 결과는 그 자체로 훌륭한 커뮤니케이션 도구입니다.
- Pull Request(PR) 설명: 의존성 라이브러리를 대규모로 업데이트하는 PR을 생성할 때, `flutter pub upgrade --dry-run`의 출력 결과를 PR 설명에 포함시키세요. 이를 통해 리뷰어들은 어떤 패키지가 어느 버전으로 변경되는지 명확하게 인지하고, 잠재적인 위험성을 더 쉽게 검토할 수 있습니다.
- 변경 사항 논의: 특정 패키지 추가가 다른 의존성에 미치는 영향에 대해 팀원과 논의해야 할 때, `flutter pub add <package> --dry-run`의 결과를 공유하며 대화를 시작하면 훨씬 더 생산적인 논의가 가능합니다.
자동화된 스크립트(CI/CD)
자동화된 빌드 및 배포 스크립트에서 --dry-run
은 안정성을 더하는 중요한 검증 단계가 될 수 있습니다.
#!/bin/bash
# 1. 의존성 업데이트 계획을 로그로 남긴다.
echo "--- Planning dependency upgrade ---"
flutter pub upgrade --dry-run
# 2. 실제 의존성 업데이트를 진행한다.
echo "--- Applying dependency upgrade ---"
flutter pub upgrade
# 3. 코드 생성 계획을 로그로 남긴다.
echo "--- Planning code generation ---"
dart run build_runner build --delete-conflicting-outputs --dry-run
# 4. 실제 코드 생성을 진행한다.
echo "--- Applying code generation ---"
dart run build_runner build --delete-conflicting-outputs
# ... 이후 빌드 및 테스트 과정 진행 ...
이러한 스크립트는 CI/CD 파이프라인 실행 로그에 각 단계에서 어떤 변경이 예정되었는지를 명확하게 기록으로 남깁니다. 만약 빌드가 실패했을 경우, 로그를 통해 어느 단계에서 문제가 발생했는지 추적하기가 훨씬 용이해집니다.
결론: 단순한 옵션을 넘어선 개발 철학
--dry-run
은 Dart와 Flutter CLI에 포함된 수많은 옵션 중 하나에 불과할 수 있습니다. 하지만 이 작은 플래그가 담고 있는 철학은 결코 작지 않습니다. 그것은 바로 신중함, 예측 가능성, 그리고 통제력입니다. 개발 과정에서 마주치는 불확실성을 줄이고, 자신의 의도가 코드와 시스템에 정확하게 반영되도록 보장하는 강력한 도구입니다.
오늘부터라도 파일 시스템이나 프로젝트의 의존성에 영향을 미치는 명령어를 실행하기 전에, 잠시 멈추고 명령어 끝에 --dry-run
을 추가하는 습관을 들여보세요. 이 작은 변화가 여러분의 코드를 잠재적인 위험으로부터 보호하고, 여러분을 더 자신감 있고 신뢰할 수 있는 개발자로 만들어 줄 것입니다. --dry-run
은 단순한 기술이 아니라, 프로페셔널한 개발자가 갖춰야 할 중요한 마음가짐이자 규율입니다.
0 개의 댓글:
Post a Comment