프로그래밍 여정에서 사용하는 기술에 관계없이 수많은 애플리케이션을 구축하게 되는데, 이러한 작업을 완료하는 것도 중요하지만 올바른 방법으로 수행하는 것이 더 중요합니다. 아래는 Flutter를 사용할때 주요 관점이 될 법한 사례들을 간단히 정리해봤습니다.
👉 가능하면 const 키워드 사용
👉 쉼표를 사용하여 코드 의도 전달
👉 명명 규칙 따르기
👉 올바른 위젯 사용
👉 가능하면 private 변수/메서드 사용
👉 상태 관리의 적절한 사용
👉 딥 트리를 사용하지 않고, 별도의 위젯 생성
👉 위젯 내에서 메서드 구현을 피하고 대신 별도의 메서드 생성
👉 nullable이 아닌 한 nullable 사용 피하기
👉 cascade(..) 사용
👉 spread 연산자(...) 사용
👉 테마/경로를 별도의 파일에 정의하기
👉 하드코딩된 문자열 사용 피하기
👉 하드코딩된 스타일 사용하지 않기
👉 앱 내에서 패키지 가져올때 가급적 상대경로로 가져오기
👉 가능하면 stateless 위젯 사용
👉 Flutter lint 사용
아래 예제에서는 동일한 로직을 구현하는 가장 좋고 올바른 방법을 표현해보았습니다.
가능하면 const 키워드 사용
const 키워드를 사용하면 위젯을 다시 빌드하지 않아도 됩니다.
❌ 기존 코드
Column(
children: [
Text('One'),
Text('Two'),
],
)
✅ 개선 코드
Column(
children: const [
Text('One'),
Text('Two'),
],
)
쉼표를 사용하여 코드 의도 전달
쉼표를 최대한 많이 추가하여 애플리케이션 코드의 의도를 올바르게 전달할 수 있습니다.
❌ 기존 코드
@override
Widget build(BuildContext context) {
return const Scaffold(body: Center(child: Text('One')));
}
✅ 개선 코드
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
'One'
),
),
);
}
이름 지정 규칙 따르기
다트에서는 다음과 같이 이름을 지을 때 명심해야 할 사항이 있습니다.
👉 파일 이름은 항상 작은 대소문자를 포함한 카멜 케이스를 사용합니다 (ex. home_page.dart, file_manager.dart 등)
👉 클래스 이름은 대문자로 시작합니다 (ex. HomePage, FileManager 등)
👉 메서드 이름은 소문자로 시작합니다 (ex. getData())
👉 필드, 메서드 또는 클래스가 비공개인 경우 밑줄을 사용합니다 (ex. String _privateValue)
❌ 기존 코드
1. 파일명
Home_Page.dart
File_manager.dart
2. 클래스명
class myApp
class myapp
3. 메서드명
void getdata() {}
void GetData() {}
4. Private 변수
String privateVariable
✅ 개선 코드
1. 파일명
home_page.dart
file_manager.dart
2. 클래스명
class MyApp
3. 메서드명
void getData() {}
4. Private 변수
String _privateVariable
올바른 위젯 사용
Flutter는 위젯에 관한 것이지만 올바른 위젯을 사용하는 것이 중요할 때도 있습니다. 예를 들어, 패딩을 얻기 위해 컨테이너 위젯을 사용하는 것은 의미가 없습니다. 대신 패딩 위젯을 사용하세요.
❌ 기존 코드
Container(
padding: const EdgeInsets.all(8),
child: const Text("Hello"),
),
Container(height: 10,),
✅ 개선 코드
const Padding(
padding: EdgeInsets.all(8),
child: Text("Hello"),
),
const SizedBox(height: 10,)
가능하면 private 변수/메서드 사용
필요한 경우가 아니라면 가급적 private 키워드를 사용하세요.
❌ 기존 코드
class Student {
String name;
String address;
Student({
required this.name,
required this.address,
});
}
✅ 개선 코드
class Student{
String _name;
String _address;
Student({
required String name,
required String address,
}) : _name = name,
_address = address;
}
상태 관리의 적절한 사용
상태 관리 도구를 사용하지 않는 경우 setState를 사용하여 위젯 트리를 다시 빌드해야 할 수 있지만 올바른 방법으로 사용하고 있는지 확인하세요. 단일 텍스트 필드를 업데이트한다고 해서 전체 위젯을 다시 빌드하지 말고 별도의 위젯을 만든 다음 해당 새 위젯만 다시 빌드하세요. 이를 상태 올리기(lifting the state up)라고도 합니다.
❌ 기존 코드
Column(
children: [
Text(
_textValue
),
ElevatedButton(
onPressed: (){
setState((){
_textValue="new value";
});
},
child: Text(
'Update'
),),
],
)
✅ 개선 코드
Column(
children: [
NewWidgetWithTextAndButton(),
],
)
딥 트리를 사용하지 않고, 별도의 위젯 생성
저도 이 문제에 여러 번 직면한 적이 있습니다. 수천 줄의 코드로 이루어진 하나의 위젯을 계속 보고 싶지는 않을 겁니다. 대신 별도의 위젯을 만들어 보세요. 깔끔하고 리팩터링하기 쉬울 거에요!
❌ 기존 코드
Column(
children: [
Container(
...
),
Container(
...
),
],
)
✅ 개선 코드
Column(
children: [
FirstLengthyCodeWidget(),
SecondLengthyCodeWidget(),
],
)
위젯 내에서 메소드 구현을 피하고 대신 별도의 메서드 생성
어떤 버튼의 onPressed() 내에 50줄의 코드가 있나요? 좋은 방법은 아닙니다. 위젯 외부의 메서드로 감싸세요.
❌ 기존 코드
@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
...
},
child: const Text('Do some task'),
),
);
}
✅ 개선 코드
@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: _callTheListener,
child: const Text('Do some task'),
),
);
}
void _callTheListener(){
...
}
nullable이 아닌 한 nullable 사용 피하기
다트의 한 가지 좋은 점은 Java와 달리 null safety를 지원한다는 것입니다. 이전 프로젝트에서는 애플리케이션에서 null safety을 제대로 사용하지 않았기 때문에 많은 null exception이 발생했습니다. 따라서 꼭 필요한 경우가 아니라면 nullable을 사용하지 마세요.
❌ 기존 코드
TextEditingController? textEditingController;@override
initState() {
super.initState();
textEditingController = TextEditingController();
}
✅ 개선 코드
late TextEditingController textEditingController;
@override
initState() {
super.initState();
textEditingController = TextEditingController();
}
Cascade(..) 사용
플러터를 처음 시작하는 경우 이 연산자를 사용하지 않았을 수도 있지만 동일한 객체에 대해 일부 작업을 수행하려는 경우 매우 유용합니다.
❌ 기존 코드
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;
✅ 개선 코드
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
Spread 연산자 (...) 사용
이것은 다트가 제공하는 또 다른 멋진 연산자입니다. 이 연산자를 사용하여 if-else, 목록에 합류 등과 같은 많은 작업을 간단히 수행할 수 있습니다.
❌ 기존 코드
@override
Widget build(BuildContext context) {
bool isTrue = true;
return Scaffold(
body: Column(
children: [
isTrue ? const Text('One') : Container(),
isTrue ? const Text('Two') : Container(),
isTrue ? const Text('Three') : Container(),
],
),
);
}
✅ 개선 코드
@override
Widget build(BuildContext context) {
bool isTrue = true;
return Scaffold(
body: Column(
children: [
if(isTrue)...[
const Text('One'),
const Text('Two'),
const Text('Three')
]
],
),
);
}
테마/경로를 별도의 파일에 정의하기
애플리케이션에 여러 개의 테마가 필요하고 앱에 많은 경로가 필요할 수 있으므로 경로와 테마를 별도의 파일에 정의하는 것이 좋습니다.
❌ 기존 코드
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: TextTheme(),
primaryColor: Colors.green,
),
home: const HomePage(),
);
}
✅ 개선 코드
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: appTheme,
home: const HomePage(),
);
}
var appTheme = ThemeData(
primarySwatch: Colors.blue,
textTheme: const TextTheme(),
primaryColor: Colors.green,
);
하드코딩된 문자열 사용 피하기
도움이 되지 않을 것 같지만 앱이 성장하기 시작하면 결국 많이 찾아보고, 계속 교체를 하게 될 것입니다.
❌ 기존 코드
Text(
'one',
),
✅ 개선 코드
Text(
AppLocalizations.of(context)!.one,
),
하드코딩된 스타일 사용하지 않기
애플리케이션에서 하드코딩된 스타일, 장식 등을 사용 중이거나 나중에 해당 스타일을 변경하기로 결정한 경우. 하나씩 수정해야 합니다.
❌ 기존 코드
Column(
children: const [
Text(
'One',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
Text(
'Two',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
],
)
✅ 개선 코드
Column(
children: [
Text(
'One',
style: Theme.of(context).textTheme.subtitle1,
),
Text(
'Two',
style: Theme.of(context).textTheme.subtitle1,
),
],
),
앱 내에서 패키지 가져올때 가급적 상대경로로 가져오기
애플리케이션 클래스 또는 파일을 가져오려는 경우 파일에서 경로를 가져올 때 패키지를 직접 가져오기보다 상대경로로 가져오는 것이 좋습니다.
❌ 기존 코드
import 'package:flutter/material.dart';
import 'package:test_project/student.dart';
✅ 개선 코드
import 'package:flutter/material.dart';
import 'model/student.dart';
가능하면 stateless 위젯 사용
가능한 한 상태 저장 위젯을 사용하지 않는 것이 좋으며, 이렇게 하면 setState를 사용하여 위젯을 반복해서 빌드할 필요가 없습니다.
❌ 기존 코드
class TestWidget extends StatefulWidget {
const TestWidget({Key? key}) : super(key: key);
@override
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
@override
Widget build(BuildContext context) {
return Image.asset('assets/men_walking.png');
}
}
✅ 개선 코드
class TestWidget extends StatelessWidget {
const TestWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Image.asset('assets/men_walking.png');
}
}
Flutter lint 사용
요즘 플러터는 코드에 대한 규칙 집합을 정의할 수 있는 플러터 린트를 기본으로 제공합니다. 애플리케이션에서 반드시 사용하세요.
👉 Flutter lint: https://pub.dev/packages/lint