Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
KISS: Keep It Simple, Stupid
KISS — это один из фундаментальных принципов в разработке программного обеспечения. Его суть: проще решение часто лучше сложного. Это не означает "тупой код", а означает, что нужно избегать ненужной сложности и излишних абстракций.
Определение
KISS расшифровывается как "Keep It Simple, Stupid" и гласит:
Большинство систем работают лучше всего, если они остаются простыми, а не усложняются. Поэтому при разработке нужно избегать ненужной сложности и делать выбор в пользу простоты.
Это особенно актуально для Flutter разработки, где излишняя сложность может привести к:
- Сложности в поддержке кода
- Большему количеству багов
- Снижению производительности
- Затруднению onboarding новых разработчиков
KISS в контексте Flutter
Пример 1: Простой счётчик
// ❌ СЛОЖНО — оверинжиниринг с миксином, генериком и кастомным контроллером
mixin Observable<T> {
late T _value;
final StreamController<T> _controller = StreamController<T>.broadcast();
T get value => _value;
Stream<T> get stream => _controller.stream;
void setValue(T newValue) {
if (_value != newValue) {
_value = newValue;
_controller.add(newValue);
}
}
void dispose() => _controller.close();
}
class AdvancedCounterService with Observable<int> {
AdvancedCounterService() {
_value = 0;
}
void increment() => setValue(value + 1);
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final service = AdvancedCounterService();
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: service.stream,
initialData: 0,
builder: (context, snapshot) {
return Text('Counter: ${snapshot.data}');
},
);
}
@override
void dispose() {
service.dispose();
super.dispose();
}
}
// ✅ ПРОСТО — понятное и работающее решение
class CounterService extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => CounterService(),
child: Scaffold(
body: Consumer<CounterService>(
builder: (context, service, child) {
return Text('Counter: ${service.counter}');
},
),
),
);
}
}
Вывод: Второе решение:
- Понятнее новому разработчику
- Меньше кода
- Легче отлаживать
- Проще добавить новый функционал
Признаки нарушения KISS
1. Многоуровневые абстракции
// ❌ Слишком много слоев
interface IRepository<T> { ... }
interface IService<T> { ... }
interface IController<T> { ... }
interface IPresenter<T> { ... }
interface IViewModel<T> { ... }
// И для каждого типа T нужна своя реализация
// ✅ Проще — только нужные слои
class UserRepository { ... }
class UserService { ... }
class UserViewModel { ... }
2. Premature Optimization (оптимизация "на будущее")
// ❌ Оптимизация, которая не нужна
class ListCache<T> {
final Map<String, List<T>> _cache = {};
final Map<String, Completer<List<T>>> _pending = {};
final Map<String, DateTime> _timestamps = {};
final Duration _ttl;
// Кэширование с TTL, дедупликацией запросов, invalidation...
// Всё это для простого списка товаров
}
// ✅ Просто используй встроенные возможности
class ProductService {
List<Product> _cachedProducts = [];
Future<List<Product>> getProducts() async {
if (_cachedProducts.isNotEmpty) {
return _cachedProducts;
}
_cachedProducts = await api.getProducts();
return _cachedProducts;
}
}
3. Обобщение без нужды
// ❌ Генерик контроллер для всего
abstract class BaseController<T, U> extends ChangeNotifier {
late T state;
late U additionalState;
void initialize() => state = getInitialState();
T getInitialState();
void updateState(T newState) => state = newState;
void resetState() => state = getInitialState();
// ...
}
class UserController extends BaseController<User?, LoadingState> {
@override
User? getInitialState() => null;
}
// ✅ Просто отдельный контроллер
class UserController extends ChangeNotifier {
User? user;
bool isLoading = false;
String? error;
Future<void> loadUser(int id) async {
isLoading = true;
notifyListeners();
try {
user = await api.getUser(id);
} catch (e) {
error = e.toString();
} finally {
isLoading = false;
notifyListeners();
}
}
}
KISS Best Practices
1. Используй встроенные решения
// ❌ Кастомный logger
class Logger {
static void log(String message) {
// Кастомная реализация с file writing, formatting, etc
}
}
// ✅ Просто используй flutter_logs или печать
import 'package:flutter_logs/flutter_logs.dart';
// Или совсем просто
print('Message');
2. Не добавляй функционал "на будущее"
// ❌ YAGNI нарушение (You Aren't Gonna Need It)
class UserRepository {
// Для фильтрации по 15 полям
Future<List<User>> search({
String? name,
int? age,
String? email,
String? phone,
String? city,
String? country,
// ... ещё 10 полей
}) async { ... }
}
// ✅ Добавь только то, что нужно сейчас
class UserRepository {
Future<List<User>> searchByName(String name) async { ... }
}
// Потом, если понадобится:
class UserRepository {
Future<List<User>> search({
String? name,
String? email,
}) async { ... }
}
3. Разумный выбор инструментов
// ❌ Redux для простого счётчика
import 'package:redux/redux.dart';
// 5 файлов: Action, Reducer, Middleware, State, Store
// ✅ Просто используй setState для простого случая
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() => _counter++),
child: Text('$_counter'),
);
}
}
KISS в коде
Пример 1: Форматирование даты
// ❌ Излишне сложно
String formatDate(DateTime date) {
final year = date.year;
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
final hour = date.hour.toString().padLeft(2, '0');
final minute = date.minute.toString().padLeft(2, '0');
final second = date.second.toString().padLeft(2, '0');
return '$day.$month.$year $hour:$minute:$second';
}
// ✅ Просто используй intl
import 'package:intl/intl.dart';
String formatDate(DateTime date) => DateFormat('dd.MM.yyyy HH:mm:ss').format(date);
// Или совсем просто
String formatDate(DateTime date) => date.toString();
Пример 2: Валидация email
// ❌ Сложный regex
Bool isValidEmail(String email) {
final pattern = r'^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_.-]+)\.([a-zA-Z]{2,6})$';
final regex = RegExp(pattern);
return regex.hasMatch(email);
}
// ✅ Просто проверь наличие @
Bool isValidEmail(String email) => email.contains('@');
// Если нужно серьёзнее — используй библиотеку
// email_validator, или просто пошли на сервер для проверки
Пример 3: Кэширование
// ❌ Кастомный кэш с TTL, eviction policy, etc
class AdvancedCache<T> {
final Map<String, CacheEntry<T>> _data = {};
final Duration _ttl;
late Timer _cleanupTimer;
AdvancedCache(this._ttl) {
_cleanupTimer = Timer.periodic(Duration(minutes: 1), (_) => _cleanup());
}
void _cleanup() { ... }
T? get(String key) { ... }
void set(String key, T value) { ... }
}
// ✅ Просто используй встроенное или простое решение
class SimpleCache<T> {
final Map<String, T> _data = {};
T? get(String key) => _data[key];
void set(String key, T value) => _data[key] = value;
void clear() => _data.clear();
}
// Или используй готовый пакет: cached_network_image
Когда нарушить KISS
Есть случаи, когда нужна сложность:
-
Реальная необходимость в масштабируемости
- Если приложение действительно будет расти
- Если есть мировая команда разработчиков
-
Performance критичны
- Если действительно нужна оптимизация
- Если проверено на профайлере (не гадание)
-
Сложная бизнес-логика
- Если правила действительно сложные
- Если нужна чистая архитектура для понимания
KISS vs YAGNI vs DRY
Эти принципы идут вместе:
// KISS — не усложняй
// DRY — не повторяй код
// YAGNI — не добавляй ненужное
// ❌ Нарушает все три
class ComplexUserHandler {
void handleUserA() { /* много кода */ }
void handleUserB() { /* повтор кода */ }
void handleUserC() { /* повтор кода */ }
// Возможно, никогда не используется
}
// ✅ Следует всем трём
void handleUser(User user) {
// Один метод
// Без дублирования
// Без ненужного функционала
}
Итог
KISS означает:
- Выбирай простое решение, если оно работает
- Избегай ненужной сложности и абстракций
- Не оптимизируй, пока не будет реального требования
- Не добавляй функции "на будущее" (YAGNI)
- Помни: код читается чаще, чем пишется
Правило Альберта Эйнштейна:
"Всё должно быть максимально простым, но не проще."
Это идеальный баланс KISS: решение должно быть простым, но достаточно гибким для текущих требований.