@su-record/vibe 0.4.6 โ 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vibe/rules/core/communication-guide.md +104 -0
- package/.vibe/rules/core/development-philosophy.md +53 -0
- package/.vibe/rules/core/quick-start.md +121 -0
- package/.vibe/rules/languages/dart-flutter.md +509 -0
- package/.vibe/rules/languages/go.md +396 -0
- package/.vibe/rules/languages/java-spring.md +586 -0
- package/.vibe/rules/languages/kotlin-android.md +491 -0
- package/.vibe/rules/languages/python-django.md +371 -0
- package/.vibe/rules/languages/python-fastapi.md +386 -0
- package/.vibe/rules/languages/rust.md +425 -0
- package/.vibe/rules/languages/swift-ios.md +516 -0
- package/.vibe/rules/languages/typescript-nextjs.md +441 -0
- package/.vibe/rules/languages/typescript-node.md +375 -0
- package/.vibe/rules/languages/typescript-react-native.md +446 -0
- package/.vibe/rules/languages/typescript-react.md +525 -0
- package/.vibe/rules/languages/typescript-vue.md +353 -0
- package/.vibe/rules/quality/bdd-contract-testing.md +388 -0
- package/.vibe/rules/quality/checklist.md +276 -0
- package/.vibe/rules/quality/testing-strategy.md +437 -0
- package/.vibe/rules/standards/anti-patterns.md +369 -0
- package/.vibe/rules/standards/code-structure.md +291 -0
- package/.vibe/rules/standards/complexity-metrics.md +312 -0
- package/.vibe/rules/standards/naming-conventions.md +198 -0
- package/.vibe/rules/tools/mcp-hi-ai-guide.md +665 -0
- package/.vibe/rules/tools/mcp-workflow.md +51 -0
- package/package.json +2 -2
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# ๐ฏ Dart + Flutter ํ์ง ๊ท์น
|
|
2
|
+
|
|
3
|
+
## ํต์ฌ ์์น (core์์ ์์)
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
โ
๋จ์ผ ์ฑ
์ (SRP)
|
|
7
|
+
โ
์ค๋ณต ์ ๊ฑฐ (DRY)
|
|
8
|
+
โ
์ฌ์ฌ์ฉ์ฑ
|
|
9
|
+
โ
๋ฎ์ ๋ณต์ก๋
|
|
10
|
+
โ
ํจ์ โค 30์ค, build() โค 50์ค
|
|
11
|
+
โ
์ค์ฒฉ โค 3๋จ๊ณ
|
|
12
|
+
โ
Cyclomatic complexity โค 10
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Dart/Flutter ํนํ ๊ท์น
|
|
16
|
+
|
|
17
|
+
### 1. Immutability ์ฐ์ (@immutable)
|
|
18
|
+
|
|
19
|
+
```dart
|
|
20
|
+
// โ Mutable ํด๋์ค
|
|
21
|
+
class User {
|
|
22
|
+
String name;
|
|
23
|
+
int age;
|
|
24
|
+
|
|
25
|
+
User({required this.name, required this.age});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// โ
Immutable ํด๋์ค + copyWith
|
|
29
|
+
@immutable
|
|
30
|
+
class User {
|
|
31
|
+
const User({
|
|
32
|
+
required this.name,
|
|
33
|
+
required this.age,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
final String name;
|
|
37
|
+
final int age;
|
|
38
|
+
|
|
39
|
+
User copyWith({
|
|
40
|
+
String? name,
|
|
41
|
+
int? age,
|
|
42
|
+
}) {
|
|
43
|
+
return User(
|
|
44
|
+
name: name ?? this.name,
|
|
45
|
+
age: age ?? this.age,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
bool operator ==(Object other) =>
|
|
51
|
+
identical(this, other) ||
|
|
52
|
+
other is User && name == other.name && age == other.age;
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
int get hashCode => name.hashCode ^ age.hashCode;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. StatelessWidget ์ ํธ
|
|
60
|
+
|
|
61
|
+
```dart
|
|
62
|
+
// โ
StatelessWidget (์์ ์์ ฏ)
|
|
63
|
+
class UserAvatar extends StatelessWidget {
|
|
64
|
+
const UserAvatar({
|
|
65
|
+
super.key,
|
|
66
|
+
required this.imageUrl,
|
|
67
|
+
this.size = 40.0,
|
|
68
|
+
this.onTap,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
final String imageUrl;
|
|
72
|
+
final double size;
|
|
73
|
+
final VoidCallback? onTap;
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
Widget build(BuildContext context) {
|
|
77
|
+
return GestureDetector(
|
|
78
|
+
onTap: onTap,
|
|
79
|
+
child: CircleAvatar(
|
|
80
|
+
radius: size / 2,
|
|
81
|
+
backgroundImage: NetworkImage(imageUrl),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// โ StatefulWidget ๋จ์ฉ (์ํ๊ฐ ์๋๋ฐ ์ฌ์ฉ)
|
|
88
|
+
class UserAvatar extends StatefulWidget {
|
|
89
|
+
// ์ํ ๊ด๋ฆฌ ๋ถํ์
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Provider ํจํด (์ํ ๊ด๋ฆฌ)
|
|
94
|
+
|
|
95
|
+
```dart
|
|
96
|
+
// โ
Immutable State + ChangeNotifier
|
|
97
|
+
@immutable
|
|
98
|
+
class FeedState {
|
|
99
|
+
const FeedState({
|
|
100
|
+
this.feeds = const [],
|
|
101
|
+
this.isLoading = false,
|
|
102
|
+
this.error,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
final List<Feed> feeds;
|
|
106
|
+
final bool isLoading;
|
|
107
|
+
final String? error;
|
|
108
|
+
|
|
109
|
+
FeedState copyWith({
|
|
110
|
+
List<Feed>? feeds,
|
|
111
|
+
bool? isLoading,
|
|
112
|
+
String? error,
|
|
113
|
+
}) {
|
|
114
|
+
return FeedState(
|
|
115
|
+
feeds: feeds ?? this.feeds,
|
|
116
|
+
isLoading: isLoading ?? this.isLoading,
|
|
117
|
+
error: error ?? this.error,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class FeedProvider extends ChangeNotifier {
|
|
123
|
+
FeedState _state = const FeedState();
|
|
124
|
+
FeedState get state => _state;
|
|
125
|
+
|
|
126
|
+
final FeedService _feedService;
|
|
127
|
+
|
|
128
|
+
FeedProvider(this._feedService);
|
|
129
|
+
|
|
130
|
+
Future<void> loadFeeds() async {
|
|
131
|
+
_state = _state.copyWith(isLoading: true, error: null);
|
|
132
|
+
notifyListeners();
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
final feeds = await _feedService.getFeeds();
|
|
136
|
+
_state = _state.copyWith(feeds: feeds, isLoading: false);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
_state = _state.copyWith(error: e.toString(), isLoading: false);
|
|
139
|
+
}
|
|
140
|
+
notifyListeners();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ์ฌ์ฉ
|
|
145
|
+
class FeedScreen extends StatelessWidget {
|
|
146
|
+
@override
|
|
147
|
+
Widget build(BuildContext context) {
|
|
148
|
+
final feedState = context.watch<FeedProvider>().state;
|
|
149
|
+
|
|
150
|
+
if (feedState.isLoading) return const CircularProgressIndicator();
|
|
151
|
+
if (feedState.error != null) return ErrorWidget(feedState.error!);
|
|
152
|
+
|
|
153
|
+
return FeedList(feeds: feedState.feeds);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 4. Null Safety ๋ช
ํํ
|
|
159
|
+
|
|
160
|
+
```dart
|
|
161
|
+
// โ
Null safety ํ์ฉ
|
|
162
|
+
class User {
|
|
163
|
+
User({
|
|
164
|
+
required this.id, // Non-nullable (ํ์)
|
|
165
|
+
required this.name,
|
|
166
|
+
this.bio, // Nullable (์ ํ)
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
final String id;
|
|
170
|
+
final String name;
|
|
171
|
+
final String? bio; // ? ๋ช
์
|
|
172
|
+
|
|
173
|
+
String getBioOrDefault() {
|
|
174
|
+
return bio ?? 'No bio'; // ?? ์ฐ์ฐ์
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
void printBio() {
|
|
178
|
+
bio?.length; // ?. ์์ ํธ์ถ
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// โ
Late ๋ณ์ (์ด๊ธฐํ ์ง์ฐ)
|
|
183
|
+
class MyWidget extends StatefulWidget {
|
|
184
|
+
@override
|
|
185
|
+
State<MyWidget> createState() => _MyWidgetState();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class _MyWidgetState extends State<MyWidget> {
|
|
189
|
+
late AnimationController _controller; // initState์์ ์ด๊ธฐํ
|
|
190
|
+
|
|
191
|
+
@override
|
|
192
|
+
void initState() {
|
|
193
|
+
super.initState();
|
|
194
|
+
_controller = AnimationController(vsync: this);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
void dispose() {
|
|
199
|
+
_controller.dispose();
|
|
200
|
+
super.dispose();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 5. ์์ ฏ ๋ถ๋ฆฌ (Extract Widget)
|
|
206
|
+
|
|
207
|
+
```dart
|
|
208
|
+
// โ ๊ธด build ๋ฉ์๋ (80์ค)
|
|
209
|
+
class UserProfile extends StatelessWidget {
|
|
210
|
+
@override
|
|
211
|
+
Widget build(BuildContext context) {
|
|
212
|
+
return Column(
|
|
213
|
+
children: [
|
|
214
|
+
// 30์ค: ํค๋
|
|
215
|
+
Container(...),
|
|
216
|
+
// 25์ค: ํต๊ณ
|
|
217
|
+
Row(...),
|
|
218
|
+
// 25์ค: ํผ๋ ๋ฆฌ์คํธ
|
|
219
|
+
ListView(...),
|
|
220
|
+
],
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// โ
์๋ธ ์์ ฏ์ผ๋ก ๋ถ๋ฆฌ
|
|
226
|
+
class UserProfile extends StatelessWidget {
|
|
227
|
+
@override
|
|
228
|
+
Widget build(BuildContext context) {
|
|
229
|
+
return Column(
|
|
230
|
+
children: [
|
|
231
|
+
const ProfileHeader(),
|
|
232
|
+
const ProfileStats(),
|
|
233
|
+
const ProfileFeedList(),
|
|
234
|
+
],
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
class ProfileHeader extends StatelessWidget {
|
|
240
|
+
const ProfileHeader({super.key});
|
|
241
|
+
|
|
242
|
+
@override
|
|
243
|
+
Widget build(BuildContext context) {
|
|
244
|
+
// ํค๋๋ง
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
class ProfileStats extends StatelessWidget {
|
|
249
|
+
const ProfileStats({super.key});
|
|
250
|
+
|
|
251
|
+
@override
|
|
252
|
+
Widget build(BuildContext context) {
|
|
253
|
+
// ํต๊ณ๋ง
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 6. ์์ ํจ์ (Static Methods)
|
|
259
|
+
|
|
260
|
+
```dart
|
|
261
|
+
// โ
์์ ํจ์ (์ํ ์์)
|
|
262
|
+
class DateUtils {
|
|
263
|
+
// Private constructor (์ธ์คํด์ค ์์ฑ ๋ฐฉ์ง)
|
|
264
|
+
DateUtils._();
|
|
265
|
+
|
|
266
|
+
static String formatRelativeTime(DateTime dateTime) {
|
|
267
|
+
final now = DateTime.now();
|
|
268
|
+
final difference = now.difference(dateTime);
|
|
269
|
+
|
|
270
|
+
if (difference.inDays > 0) return '${difference.inDays}์ผ ์ ';
|
|
271
|
+
if (difference.inHours > 0) return '${difference.inHours}์๊ฐ ์ ';
|
|
272
|
+
return '${difference.inMinutes}๋ถ ์ ';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
static bool isToday(DateTime dateTime) {
|
|
276
|
+
final now = DateTime.now();
|
|
277
|
+
return dateTime.year == now.year &&
|
|
278
|
+
dateTime.month == now.month &&
|
|
279
|
+
dateTime.day == now.day;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ์ฌ์ฉ
|
|
284
|
+
final formatted = DateUtils.formatRelativeTime(feed.createdAt);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 7. ์๋ฌ ์ฒ๋ฆฌ (Result/Either ํจํด)
|
|
288
|
+
|
|
289
|
+
```dart
|
|
290
|
+
// โ
Result ํ์
์ผ๋ก ์๋ฌ ์ฒ๋ฆฌ
|
|
291
|
+
sealed class Result<T> {
|
|
292
|
+
const Result();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
class Success<T> extends Result<T> {
|
|
296
|
+
const Success(this.value);
|
|
297
|
+
final T value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
class Failure<T> extends Result<T> {
|
|
301
|
+
const Failure(this.error);
|
|
302
|
+
final String error;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ์ฌ์ฉ
|
|
306
|
+
Future<Result<User>> login(String email, String password) async {
|
|
307
|
+
try {
|
|
308
|
+
final user = await _authService.login(email, password);
|
|
309
|
+
return Success(user);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
return Failure(e.toString());
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ํธ์ถ๋ถ (Pattern matching)
|
|
316
|
+
final result = await login(email, password);
|
|
317
|
+
switch (result) {
|
|
318
|
+
case Success(:final value):
|
|
319
|
+
Navigator.pushReplacement(context, HomePage(user: value));
|
|
320
|
+
case Failure(:final error):
|
|
321
|
+
showErrorDialog(context, error);
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 8. Extension Methods
|
|
326
|
+
|
|
327
|
+
```dart
|
|
328
|
+
// โ
Extension์ผ๋ก ๊ธฐ๋ฅ ํ์ฅ
|
|
329
|
+
extension StringExtension on String {
|
|
330
|
+
String capitalize() {
|
|
331
|
+
if (isEmpty) return this;
|
|
332
|
+
return '${this[0].toUpperCase()}${substring(1)}';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
bool get isEmail {
|
|
336
|
+
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
|
337
|
+
return emailRegex.hasMatch(this);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
extension ListExtension<T> on List<T> {
|
|
342
|
+
List<T> distinctBy<K>(K Function(T) keySelector) {
|
|
343
|
+
final seen = <K>{};
|
|
344
|
+
return where((item) => seen.add(keySelector(item))).toList();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ์ฌ์ฉ
|
|
349
|
+
final name = 'john'.capitalize(); // 'John'
|
|
350
|
+
final isValid = 'test@example.com'.isEmail; // true
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 9. const Constructor ํ์ฉ
|
|
354
|
+
|
|
355
|
+
```dart
|
|
356
|
+
// โ
const constructor (์ปดํ์ผ ํ์ ์์)
|
|
357
|
+
class AppColors {
|
|
358
|
+
const AppColors._();
|
|
359
|
+
|
|
360
|
+
static const primary = Color(0xFF6200EE);
|
|
361
|
+
static const secondary = Color(0xFF03DAC6);
|
|
362
|
+
static const error = Color(0xFFB00020);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
class Spacing {
|
|
366
|
+
const Spacing._();
|
|
367
|
+
|
|
368
|
+
static const xs = 4.0;
|
|
369
|
+
static const sm = 8.0;
|
|
370
|
+
static const md = 16.0;
|
|
371
|
+
static const lg = 24.0;
|
|
372
|
+
static const xl = 32.0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// โ
const ์์ ฏ (์ฌ์ฌ์ฉ ์ ์ฑ๋ฅ ํฅ์)
|
|
376
|
+
class LoadingIndicator extends StatelessWidget {
|
|
377
|
+
const LoadingIndicator({super.key});
|
|
378
|
+
|
|
379
|
+
@override
|
|
380
|
+
Widget build(BuildContext context) {
|
|
381
|
+
return const Center(
|
|
382
|
+
child: CircularProgressIndicator(),
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ์ฌ์ฉ
|
|
388
|
+
const LoadingIndicator() // const๋ก ์์ฑ
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### 10. ๋น๋๊ธฐ ์ฒ๋ฆฌ (Future/Stream)
|
|
392
|
+
|
|
393
|
+
```dart
|
|
394
|
+
// โ
Future (๋จ์ผ ๋น๋๊ธฐ ์์
)
|
|
395
|
+
Future<List<Feed>> fetchFeeds() async {
|
|
396
|
+
final response = await dio.get('/api/feeds');
|
|
397
|
+
return (response.data as List)
|
|
398
|
+
.map((json) => Feed.fromJson(json))
|
|
399
|
+
.toList();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// โ
Stream (์ฐ์ ๋น๋๊ธฐ ์ด๋ฒคํธ)
|
|
403
|
+
Stream<List<Feed>> watchFeeds() {
|
|
404
|
+
return Stream.periodic(
|
|
405
|
+
const Duration(seconds: 30),
|
|
406
|
+
(_) => fetchFeeds(),
|
|
407
|
+
).asyncMap((future) => future);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// โ
StreamBuilder ์ฌ์ฉ
|
|
411
|
+
class FeedStream extends StatelessWidget {
|
|
412
|
+
@override
|
|
413
|
+
Widget build(BuildContext context) {
|
|
414
|
+
return StreamBuilder<List<Feed>>(
|
|
415
|
+
stream: watchFeeds(),
|
|
416
|
+
builder: (context, snapshot) {
|
|
417
|
+
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
418
|
+
return const LoadingIndicator();
|
|
419
|
+
}
|
|
420
|
+
if (snapshot.hasError) {
|
|
421
|
+
return ErrorWidget(snapshot.error.toString());
|
|
422
|
+
}
|
|
423
|
+
if (!snapshot.hasData) {
|
|
424
|
+
return const EmptyState();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return FeedList(feeds: snapshot.data!);
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## ์ํฐํจํด
|
|
435
|
+
|
|
436
|
+
```dart
|
|
437
|
+
// โ Mutable state
|
|
438
|
+
class BadCounter extends StatefulWidget {
|
|
439
|
+
int count = 0; // ์ํ! StatefulWidget์ ์ฌ์์ฑ๋ ์ ์์
|
|
440
|
+
|
|
441
|
+
@override
|
|
442
|
+
State<BadCounter> createState() => _BadCounterState();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// โ BuildContext๋ฅผ async gap ๋๋จธ์์ ์ฌ์ฉ
|
|
446
|
+
Future<void> badNavigate() async {
|
|
447
|
+
await Future.delayed(Duration(seconds: 1));
|
|
448
|
+
Navigator.push(context, ...); // โ context๊ฐ ๋ฌดํจํ๋์ ์ ์์
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// โ
mounted ์ฒดํฌ
|
|
452
|
+
Future<void> goodNavigate() async {
|
|
453
|
+
await Future.delayed(Duration(seconds: 1));
|
|
454
|
+
if (!mounted) return;
|
|
455
|
+
Navigator.push(context, ...);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// โ setState์์ ๊ธด ์์
|
|
459
|
+
setState(() {
|
|
460
|
+
// 10์ค์ ๋ณต์กํ ๊ณ์ฐ โ
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// โ
๊ณ์ฐ ํ setState
|
|
464
|
+
final newValue = expensiveCalculation();
|
|
465
|
+
setState(() {
|
|
466
|
+
_value = newValue; // ๊ฐ๋จํ ํ ๋น๋ง
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// โ GlobalKey ๋จ์ฉ
|
|
470
|
+
final GlobalKey<FormState> _formKey = GlobalKey();
|
|
471
|
+
|
|
472
|
+
// โ
Controller ์ฌ์ฉ
|
|
473
|
+
final TextEditingController _controller = TextEditingController();
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## ์ฝ๋ ํ์ง ๋๊ตฌ
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
# ๋ถ์
|
|
480
|
+
flutter analyze
|
|
481
|
+
|
|
482
|
+
# ํฌ๋งทํ
|
|
483
|
+
dart format .
|
|
484
|
+
|
|
485
|
+
# ํ
์คํธ
|
|
486
|
+
flutter test
|
|
487
|
+
flutter test --coverage
|
|
488
|
+
|
|
489
|
+
# ๋น๋
|
|
490
|
+
flutter build apk --release
|
|
491
|
+
flutter build ios --release
|
|
492
|
+
flutter build web --release
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## ์ฒดํฌ๋ฆฌ์คํธ
|
|
496
|
+
|
|
497
|
+
Dart/Flutter ์ฝ๋ ์์ฑ ์:
|
|
498
|
+
|
|
499
|
+
- [ ] @immutable + copyWith ํจํด
|
|
500
|
+
- [ ] StatelessWidget ์ฐ์ ์ฌ์ฉ
|
|
501
|
+
- [ ] Provider๋ก ์ํ ๊ด๋ฆฌ ๋ถ๋ฆฌ
|
|
502
|
+
- [ ] Null safety (?, ??, ?., !)
|
|
503
|
+
- [ ] build() โค 50์ค (์์ ฏ ๋ถ๋ฆฌ)
|
|
504
|
+
- [ ] ์์ ํจ์ (static methods)
|
|
505
|
+
- [ ] Result ํ์
์ผ๋ก ์๋ฌ ์ฒ๋ฆฌ
|
|
506
|
+
- [ ] Extension methods ํ์ฉ
|
|
507
|
+
- [ ] const constructor ์ฌ์ฉ
|
|
508
|
+
- [ ] Future/Stream ์ ์ ํ ์ ํ
|
|
509
|
+
- [ ] ๋ณต์ก๋ โค 10
|