Módulo 01: Fundamentos y Buenas Prácticas

Dry Kiss

Principios Diseno

# DRY y KISS - Principios de Simplicidad ## DRY - Don't Repeat Yourself ### No Te Repitas > "Cada pieza de conocimiento debe tener una representación única, sin ambigüedades y autoritativa dentro del sistema"

Comparación Visual

```mermaid graph TD subgraph "❌ WET (Write Everything Twice)" A[Logic A] B[Logic A (Copy)] C[Logic A (Copy)] A --> Bug[Fix Bug] B --> Bug2[Bug Remains] C --> Bug3[Bug Remains] end subgraph "✅ DRY (Don't Repeat Yourself)" Shared[Shared Logic] Shared --> Use1[Usage 1] Shared --> Use2[Usage 2] Shared --> Use3[Usage 3] Shared --> Fix[Fix Bug Once] end style A fill:#f99,stroke:#333,stroke-width:2px style B fill:#f99,stroke:#333,stroke-width:2px style C fill:#f99,stroke:#333,stroke-width:2px style Shared fill:#dfd,stroke:#333,stroke-width:2px ``` ### ❌ Código con Repetición ```dart class UserProfile extends StatelessWidget { final User user; @override Widget build(BuildContext context) { return Column( children: [ Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Text(user.name), ), SizedBox(height: 16), Container( padding: EdgeInsets.all(16), // ❌ Repetido decoration: BoxDecoration( // ❌ Repetido color: Colors.white, // ❌ Repetido borderRadius: BorderRadius.circular(12), // ❌ Repetido boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Text(user.email), ), SizedBox(height: 16), Container( padding: EdgeInsets.all(16), // ❌ Repetido decoration: BoxDecoration( // ❌ Repetido color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Text(user.phone), ), ], ); } } ``` ### ✅ Aplicando DRY ```dart // Extraer el widget reutilizable class CardContainer extends StatelessWidget { final Widget child; const CardContainer({required this.child}); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: child, ); } } // Usar el widget reutilizable class UserProfile extends StatelessWidget { final User user; @override Widget build(BuildContext context) { return Column( children: [ CardContainer(child: Text(user.name)), SizedBox(height: 16), CardContainer(child: Text(user.email)), SizedBox(height: 16), CardContainer(child: Text(user.phone)), ], ); } } ``` ### DRY en Lógica de Negocio #### ❌ Sin DRY ```dart class UserService { Future createUser(String name, String email) async { // Validación repetida if (!email.contains('@')) { throw ValidationException('Invalid email'); } if (name.length < 2) { throw ValidationException('Name too short'); } return repository.create(User(name: name, email: email)); } Future updateUser(String id, String name, String email) async { // ❌ Misma validación repetida if (!email.contains('@')) { throw ValidationException('Invalid email'); } if (name.length < 2) { throw ValidationException('Name too short'); } return repository.update(id, User(name: name, email: email)); } } ``` #### ✅ Con DRY ```dart class UserValidator { static void validateEmail(String email) { if (!email.contains('@')) { throw ValidationException('Invalid email'); } } static void validateName(String name) { if (name.length < 2) { throw ValidationException('Name too short'); } } static void validateUser(String name, String email) { validateName(name); validateEmail(email); } } class UserService { Future createUser(String name, String email) async { UserValidator.validateUser(name, email); // ✅ Reutilizado return repository.create(User(name: name, email: email)); } Future updateUser(String id, String name, String email) async { UserValidator.validateUser(name, email); // ✅ Reutilizado return repository.update(id, User(name: name, email: email)); } } ``` ### DRY con Extensiones ```dart // Crear extensiones para código repetido extension StringExtensions on String { bool get isValidEmail { final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); return emailRegex.hasMatch(this); } String capitalize() { if (isEmpty) return this; return '${this[0].toUpperCase()}${substring(1)}'; } } extension DateTimeExtensions on DateTime { String get formattedDate => '${day.toString().padLeft(2, '0')}/${month.toString().padLeft(2, '0')}/$year'; bool get isToday { final now = DateTime.now(); return year == now.year && month == now.month && day == now.day; } } // Uso final email = '[email protected]'; if (email.isValidEmail) { print('Valid email'); } final name = 'john'; print(name.capitalize()); // John final date = DateTime.now(); print(date.formattedDate); // 28/11/2025 ``` ## KISS - Keep It Simple, Stupid ### Mantenlo Simple > "La simplicidad debe ser un objetivo clave en el diseño, y la complejidad innecesaria debe evitarse" ### ❌ Código Complicado ```dart class ComplexUserValidator { bool validateUser(Map userData) { // ❌ Demasiado complejo return ((userData['email'] as String?)?.isNotEmpty ?? false) && ((userData['email'] as String?)?.contains('@') ?? false) && ((userData['email'] as String?)?.split('@').length == 2) && ((userData['email'] as String?)?.split('@')[1].contains('.') ?? false) && ((userData['name'] as String?)?.isNotEmpty ?? false) && ((userData['name'] as String?)?.length ?? 0) >= 2 && ((userData['age'] as int?) != null) && ((userData['age'] as int?) ?? 0) >= 18 && ((userData['age'] as int?) ?? 0) <= 120; } } ``` ### ✅ Código Simple ```dart class User { final String email; final String name; final int age; User({required this.email, required this.name, required this.age}); // Validación simple y clara bool get hasValidEmail { final parts=email.split('@'); return parts.length==2 && parts[1].contains('.'); } bool get hasValidName=> name.length >= 2; bool get hasValidAge => age >= 18 && age <= 120; bool get isValid=> hasValidEmail && hasValidName && hasValidAge; } // Uso simple final user = User(email: '[email protected]', name: 'John', age: 25); if (user.isValid) { print('User is valid'); } ``` ### KISS en Arquitectura #### ❌ Sobre-Ingeniería ```dart // ❌ Demasiadas capas para una app simple abstract class GetUserUseCaseInterface { Future> execute(GetUserParams params); } class GetUserParams { final String userId; GetUserParams(this.userId); } class GetUserUseCaseImpl implements GetUserUseCaseInterface { final UserRepositoryInterface repository; @override Future> execute(GetUserParams params) { return repository.getUser(params.userId); } } // Para una app simple, esto es excesivo ``` #### ✅ Simple y Suficiente ```dart // ✅ Para apps simples, esto puede ser suficiente class UserService { final http.Client client; UserService(this.client); Future getUser(String id) async { final response = await client.get(Uri.parse('/users/$id')); if (response.statusCode == 200) { return User.fromJson(json.decode(response.body)); } throw Exception('Failed to load user'); } } ``` ### KISS en Widgets #### ❌ Widget Complejo ```dart class ComplexButton extends StatelessWidget { final VoidCallback? onPressed; final String text; final Color? backgroundColor; final Color? textColor; final double? fontSize; final FontWeight? fontWeight; final EdgeInsets? padding; final BorderRadius? borderRadius; final double? elevation; final BoxShadow? shadow; final Gradient? gradient; // ... 20 parámetros más // Constructor gigante const ComplexButton({ required this.onPressed, required this.text, this.backgroundColor, this.textColor, this.fontSize, this.fontWeight, this.padding, this.borderRadius, this.elevation, this.shadow, this.gradient, // ... }); @override Widget build(BuildContext context) { // 200 líneas de lógica compleja return Container(/* ... */); } } ``` #### ✅ Widget Simple ```dart // ✅ Simple con valores predeterminados sensatos class SimpleButton extends StatelessWidget { final VoidCallback? onPressed; final String text; final ButtonStyle? style; // Permite personalización sin explotar parámetros const SimpleButton({ required this.onPressed, required this.text, this.style, }); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, style: style, child: Text(text), ); } } // Para casos especiales, usa el ElevatedButton directamente ``` ## Equilibrio entre DRY y KISS ### Cuando NO aplicar DRY ```dart // A veces, un poco de repetición es mejor que abstracción prematura // ❌ Abstracción prematura class ConfigurableValidator { final List> rules; final T Function(Map) parser; // ... código muy genérico y complejo } // ✅ Simple y directo (aunque se repita un poco) class EmailValidator { bool isValid(String email) => email.contains('@'); } class PasswordValidator { bool isValid(String password) => password.length >= 8; } ``` ## Mejores Prácticas ### ✅ Hacer 1. **Extraer funciones/widgets reutilizables** 2. **Usar constantes para valores repetidos** 3. **Mantener funciones pequeñas y enfocadas** 4. **Preferir claridad sobre brevedad** 5. **Empezar simple, refactorizar cuando sea necesario** ```dart // Constantes para valores repetidos class AppConstants { static const defaultPadding = 16.0; static const borderRadius = 12.0; static const animationDuration = Duration(milliseconds: 300); } // Funciones pequeñas y enfocadas bool isValidEmail(String email) => email.contains('@') && email.contains('.'); bool isValidPassword(String password) => password.length >= 8; bool isAdult(int age) => age >= 18; ``` ### ❌ Evitar 1. **Abstracciones prematuras** 2. **Código "clever" difícil de leer** 3. **Sobre-ingeniería de soluciones simples** 4. **Copiar-pegar código sin entenderlo** ```dart // ❌ Código "clever" difícil de leer final result = list.fold>>({}, (map, item) => {...map, item.key: [...?map[item.key], item.value]}); // ✅ Código simple y claro final result = >{}; for (final item in list) { result[item.key] ??= []; result[item.key]!.add(item.value); } ``` ## Regla de Tres > "Si escribes algo dos veces, está bien. Si lo escribes tres veces, extráelo" ```dart // Primera vez: OK, escríbelo inline Text(user.name); // Segunda vez: OK todavía Text(product.name); // Tercera vez: Hora de extraer class NameText extends StatelessWidget { final String name; const NameText(this.name); @override Widget build(BuildContext context) => Text(name); } ``` ## Conclusión - **DRY**: Reduce duplicación, pero no sacrifiques claridad - **KISS**: Empieza simple, agrega complejidad solo cuando sea necesario - **Balance**: La mejor solución es la más simple que resuelve el problema > "La perfección se alcanza no cuando no hay nada más que agregar, sino cuando no hay nada más que quitar" - Antoine de Saint-Exupéry

Este apartado profundiza en los conceptos clave, proporcionando ejemplos prácticos y mejores prácticas para su aplicación en proyectos reales.

Al comprender estos detalles, podrás diseñar soluciones más robustas, mantenibles y escalables en tus aplicaciones Flutter y Dart.