Irbisa · Шпаргалка
SOLID и принципы проектирования
Принципы SOLID в Dart и Flutter, DRY, KISS и YAGNI, связность и зацепление, закон Деметры, внедрение зависимостей и service locator, code smells и рефакторинг
Что означают DRY, KISS и YAGNI, и когда устранение дублирования делает код хуже?
Что такое связанность (coupling) и связность (cohesion), и почему нужны низкая связанность и высокая связность?
Что такое «запах кода» (code smell), и какие запахи чаще всего встречаются во Flutter-коде?
Что такое рефакторинг, и как не дать рефакторингу изменить поведение?
Stateэкрана оформления заказа разросся до 1000 строк: он валидирует форму, вызывает API, форматирует цены и строит UI. Как применить к нему Single Responsibility?// Formatting: pure, so the cart and the receipt print prices the same wayclass PriceFormatter { const PriceFormatter(this.currency); final String currency; String format(int cents) => '${(cents / 100).toStringAsFixed(2)} $currency';}…Как принцип открытости/закрытости (Open/Closed) выглядит во Flutter для переиспользуемого виджета и для сервиса?
// before: a flag for every screen that needed something different// ProductCard(product, showBadge: true, isCompact: false, showFavorite: true) // after: slots, so a screen extends the card without editing itclass ProductCard extends StatelessWidget { const ProductCard(…Какой принцип SOLID нарушает
LimitedCart, и почемуbuyAgainвозвращает 5 для заказа из пяти товаров, когда вLimitedCart(3)лежат только 3?class Cart { final _items = <String>[]; int get count => _items.length; void add(String sku) => _items.add(sku);} class LimitedCart extends Cart { LimitedCart(this.max); final int max; @override void add(String sku) { if (count < max) super.add(sku); // full: quietly does nothing }} // "Buy again" re-adds a past order and reports how many items went inint buyAgain(Cart cart, List<String> skus) { skus.forEach(cart.add); return skus.length;}Виджет-тесты проходят на фейковом репозитории, а настоящий в продакшене ведёт себя иначе. Как сделать так, чтобы фейк действительно можно было подставить вместо настоящего?
// test/orders_repository_contract.dart: written oncevoid ordersRepositoryContract(Future<OrdersRepository> Function() create) { late OrdersRepository repo; setUp(() async => repo = await create()); test('a placed order is returned by the next read', () async {…В модели есть
PremiumUser extends User, а экраны проверяютuser is PremiumUser. Что ломается, когда пользователь покупает подписку посреди сессии, и как это смоделировать?enum Feature { noAds, offlineDownloads } // The plan is data that changes; the user stays the same usersealed class Plan { const Plan(this.features); final Set<Feature> features;…Как принцип разделения интерфейсов (Interface Segregation) работает в Dart, где каждый класс уже является неявным интерфейсом?
// Two roles instead of one fat OrdersServiceabstract interface class OrderHistory { Future<List<Order>> recent({int limit = 20});} abstract interface class OrderPlacement {…Чем отличаются инверсия зависимостей (Dependency Inversion), внедрение зависимостей (DI) и сервис-локатор?
// Injection without inversion: passed in, but shaped by Dio's APIclass OrdersViewModelA { OrdersViewModelA(this._api); final DioOrdersApi _api; Future<int> count() async => (await _api.getOrders()).length;}…Каждый класс в Dart уже является неявным интерфейсом. Когда стоит писать отдельный
abstract interface classс единственнымImpl, а когда это пустая церемония?// One concrete class: no ClockService plus ClockServiceImplclass Clock { const Clock(); DateTime now() => DateTime.now();}…Что говорят закон Деметры и принцип «говори, а не спрашивай» (tell, don't ask), и что не так с
context.read<Session>().user!.cart.items.add(item)?// before: the button knows how the session is built and the cart's rulesvoid onAddPressedBefore(BuildContext context, Item item) { final cart = context.read<Session>().user!.cart; if (cart.items.length < 10) { cart.items.add(item); cart.total += item.priceCents;…Найдите баг: экран настроек бросает
LateInitializationError, но только когда приложение открыли из push-уведомления.class Prefs { late SharedPreferences _prefs; Future<void> init() async => _prefs = await SharedPreferences.getInstance(); bool get darkMode => _prefs.getBool('dark_mode') ?? false;} final prefs = Prefs(); // SettingsPage reads prefs.darkMode in build class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); prefs.init(); } @override Widget build(BuildContext context) => const FeedView();}Какие запахи кода вы видите в этом вызове
placeOrder, и какой рефакторинг лечит каждый из них?Future<String> placeOrder(String userId, String productId, int quantity, String street, String city, String zip, double price, String currency, bool isGift, bool express) async { return 'order-1'; // creates the order and returns its id} Future<String> onBuyPressed(User user, Product product) => placeOrder(user.id, product.id, 1, 'Abay Ave 10', 'Almaty', '050000', 19.99, 'USD', true, false);Файл
utils.dartразросся до 2000 строк, и его импортирует каждая фича. Что с ним не так, и куда должны переехать его функции?Каждый репозиторий наследует дженерик
BaseRepository<T>с пятью CRUD-методами. Через год половина переопределений бросаетUnimplementedError. Что пошло не так?// before: every repository has to pretend it supports everythingabstract class BaseRepository<T> { Future<List<T>> getAll(); Future<T?> getById(String id); Future<void> delete(String id); // plus create() and update()}…Вы выносите пакет
payments, которым пользуются три приложения. Как спроектировать его публичный API, чтобы внутренности можно было менять без мажорного релиза?// lib/payments.dart: the only file apps importexport 'src/charge_result.dart' show ChargeResult, Charged, Declined;export 'src/money.dart' show Money;export 'src/payments.dart' show Payments; // lib/src/payments.dart…