Convention de code - ApplETS/Notre-Dame GitHub Wiki
Ce document définit les standards de code pour maintenir la cohérence et la qualité du projet.
# Formater automatiquement le code
dart format lib testConfiguration : Utiliser les paramètres par défaut de Dart.
Indentation : 2 espaces (standard Dart)
// ✅ BON
Widget build(BuildContext context) {
return Container(
child: Text('Hello'),
);
}
// ❌ MAUVAIS (4 espaces ou tabs)
Widget build(BuildContext context) {
return Container(
child: Text('Hello'),
);
}Maximum : 80-100 caractères (convention Dart)
// ✅ BON (lisible sur 100 caractères)
const String veryLongVariableName = 'This is a moderately long string';
// ❌ MAUVAIS (trop long)
const String veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName = 'This is a string';Ordre des imports :
- Dart standard library
- Packages Flutter
- Packages externes
- Imports relatifs (du projet)
Utiliser : import_sorter pour l'automatisation
# Rouler import sorter
dart run import_sorter:main
// ✅ PascalCase
class CourseViewModel {}
class UserRepository {}
abstract class DataService {}
// ❌ MAUVAIS
class courseViewModel {}
class user_repository {}
class dataservice {}// ✅ camelCase
String userName;
void loadCourses() {}
int getUserAge() {}
// ❌ MAUVAIS
String user_name;
void loadcourses() {}
int GetUserAge() {}// ✅ Const globals en camelCase ou UPPER_SNAKE_CASE
const String appName = 'ÉTS Mobile';
const String apiBaseUrl = 'https://api.signets.ets.org';
// Ou pour les constantes très importantes
const int DEFAULT_TIMEOUT = 30;
// ❌ MAUVAIS
const String AppName = 'ÉTS Mobile';
const string API_BASE_URL = 'https://...';// ✅ snake_case avec underscore
lib/ui/schedule/view_model/schedule_viewmodel.dart
lib/data/repositories/course_repository.dart
lib/domain/models/course.dart
// ❌ MAUVAIS
lib/ui/schedule/ScheduleViewModel.dart
lib/data/repositories/CourseRepository.dart
// ✅ Dans RouterPaths class
class RouterPaths {
static const String schedule = '/schedule';
static const String newsDetails = '/news/:id';
}
// ❌ MAUVAIS
final scheduleRoute = '/schedule'; // Variable globaleclass MyWidget extends StatelessWidget {
// 1. Constantes statiques
static const String defaultValue = 'default';
// 2. Variables finales (immutables)
final String name;
final int age;
// 3. Variables privées (mutable)
late String _status;
// 4. Constructeur principal
const MyWidget({
required this.name,
required this.age,
});
// 5. Constructors nommés
MyWidget.empty()
: name = '',
age = 0;
// 6. Getters et Setters
String get displayName => name.toUpperCase();
// 7. Méthodes publiques
@override
Widget build(BuildContext context) {
return Container();
}
// 8. Méthodes privées
void _initialize() {}
// 9. Overrides
@override
String toString() => 'MyWidget($name, $age)';
}Diviser les longues fonctions :
// ❌ MAUVAIS (150 lignes dans build())
Widget build(BuildContext context) {
// ... 150 lignes de code ...
}
// ✅ BON (extraire en méthodes privées)
@override
Widget build(BuildContext context) {
return Column(
children: [
_buildHeader(),
_buildContent(),
_buildFooter(),
],
);
}
Widget _buildHeader() => /* ... */;
Widget _buildContent() => /* ... */;
Widget _buildFooter() => /* ... */;Documenter les classes, méthodes et parties de codes qui peuvent être plus complexes
/// True si la session est actuellement en cours
bool get isActive => DateTime.now().isAfter(startDate);
/// Cache des cours par sessionCode pour éviter les appels API répétés
final Map<String, List<Course>> _courseCache = {};
/// TODO: Implémenter le rafraîchissement automatique du cache
void _cacheRefresh() {}// ✅ TOUJOURS utiliser const si possible
const Text('Hello')
const SizedBox(height: 16)
// ❌ ÉVITER
Text('Hello')
SizedBox(height: 16)// ✅ BON : Spécifier les types
List<Course> courses = [];
Map<String, String> headers = {};
// ❌ MAUVAIS : Utiliser var ou dynamic
var courses = [];
dynamic headers = {};// ✅ BON : Utiliser final
class User {
final String name;
final String email;
}
// ❌ MAUVAIS : Variables mutables
class User {
String name = '';
String email = '';
}// ✅ BON : Attraper les exceptions spécifiques
try {
await apiClient.getUser();
} on DioException catch (e) {
print('Network error: $e');
} catch (e) {
print('Unexpected error: $e');
}
// ❌ MAUVAIS : Attraper tout génériquement
try {
await apiClient.getUser();
} catch (e) {
// On ne sait pas ce qui s'est passé
}// Fichier: test/domain/models/course_test.dart
void main() {
group('Course', () {
group('constructor', () {
test('creates instance with valid data', () {
expect(() => Course(code: 'LOG123', title: 'Prog'), returnsNormally);
});
test('throws AssertionError when code is empty', () {
expect(
() => Course(code: '', title: 'Prog'),
throwsAssertionError,
);
});
});
group('getLetterGrade', () {
test('returns A for score >= 85', () {
final course = Course(code: 'LOG123', title: 'Prog');
expect(course.getLetterGrade(90), equals('A'));
});
});
});
}# Analyser le code
flutter analyze
# Ou avec Dart
dart analyze# Formatter tout le code
dart format lib test
# Analyser les warnings/errors
flutter analyze
# Générer le code (build_runner)
flutter pub run build_runner build
# Trier les imports
flutter pub run import_sorter:main
# Exécuter les tests
flutter test
# Vérifier la couverture de test
flutter test --coverage| Élément | Convention | Exemple |
|---|---|---|
| Classes | PascalCase | UserRepository |
| Fonctions | camelCase | getUserData() |
| Variables | camelCase | userName |
| Constantes | camelCase | defaultTimeout |
| Fichiers | snake_case | user_repository.dart |
| Imports | Organisés | Dart → Flutter → Packages → Relatif |
| Indentation | 2 espaces | |
| Const | Par défaut | Autant que possible |
| Null Safety | Strict | ? et ! correctement |
| Lignes | Max 100 chars |
Cette page a été en partie générée avec l'aide de Claude Haiku 4.5