← Назад к вопросам

Что такое KISS?

1.3 Junior🔥 81 комментариев
#ООП и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Есть случаи, когда нужна сложность:

  1. Реальная необходимость в масштабируемости

    • Если приложение действительно будет расти
    • Если есть мировая команда разработчиков
  2. Performance критичны

    • Если действительно нужна оптимизация
    • Если проверено на профайлере (не гадание)
  3. Сложная бизнес-логика

    • Если правила действительно сложные
    • Если нужна чистая архитектура для понимания

KISS vs YAGNI vs DRY

Эти принципы идут вместе:

// KISS — не усложняй
// DRY — не повторяй код
// YAGNI — не добавляй ненужное

// ❌ Нарушает все три
class ComplexUserHandler {
  void handleUserA() { /* много кода */ }
  void handleUserB() { /* повтор кода */ }
  void handleUserC() { /* повтор кода */ }
  // Возможно, никогда не используется
}

// ✅ Следует всем трём
void handleUser(User user) {
  // Один метод
  // Без дублирования
  // Без ненужного функционала
}

Итог

KISS означает:

  • Выбирай простое решение, если оно работает
  • Избегай ненужной сложности и абстракций
  • Не оптимизируй, пока не будет реального требования
  • Не добавляй функции "на будущее" (YAGNI)
  • Помни: код читается чаще, чем пишется

Правило Альберта Эйнштейна:

"Всё должно быть максимально простым, но не проще."

Это идеальный баланс KISS: решение должно быть простым, но достаточно гибким для текущих требований.

Что такое KISS? | PrepBro