Irbisa · Cheatsheet

SOLID & Design Principles

The SOLID principles in Dart and Flutter, DRY, KISS and YAGNI, coupling and cohesion, the Law of Demeter, dependency injection vs service locator, code smells and refactoring

Middle Developer · 18 questions

  1. 01

    What do DRY, KISS and YAGNI mean, and when does removing duplication make code worse?

    DRY: every piece of knowledge has one home.

  2. 02

    What are coupling and cohesion, and why do you want low coupling and high cohesion?

    Coupling is how much one module depends on another's details; cohesion is how closely the things inside one module belong together.

  3. 03

    What is a code smell, and which ones do you meet most often in Flutter code?

    A code smell is a surface sign that the design may be wrong: the code works, but it will be hard to change.

  4. 04

    What is refactoring, and how do you keep a refactor from changing behaviour?

    Refactoring changes the structure of code without changing what it does.

  5. 05

    Your checkout State is 1,000 lines: it validates the form, calls the API, formats prices and builds the UI. How do you apply Single Responsibility?

    Split it by reason to change: the validation rules, the API call, the price format and the layout change at different times for different people.

    // 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';}…
  6. 06

    What does the Open/Closed Principle look like in Flutter, for a reusable widget and for a service?

    Open for extension, closed for modification: a new case arrives as new code plugged into an existing extension point, not as another edit to code that already works.

    // 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(…
  7. 07

    Which SOLID principle does LimitedCart break, and why does buyAgain return 5 for a five-item order when a LimitedCart(3) holds only 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;}

    Liskov Substitution.

  8. 08

    Your widget tests pass against a fake repository, but the real one behaves differently in production. How do you keep the fake substitutable?

    Treat the fake as one more implementation that must honour the same contract, and prove it: write the contract once as a shared test suite and run it against both the fake and the real repository.

    // 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 {…
  9. 09

    Your model has PremiumUser extends User, and screens check user is PremiumUser. What breaks when a user upgrades mid-session, and how would you model it?

    An object cannot change its class, so an upgrade means replacing the User, and every screen, cache and provider holding the old one keeps showing ads.

    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;…
  10. 10

    How does the Interface Segregation Principle apply in Dart, where every class is already an implicit interface?

    Clients should not depend on methods they do not call.

    // Two roles instead of one fat OrdersServiceabstract interface class OrderHistory {  Future<List<Order>> recent({int limit = 20});} abstract interface class OrderPlacement {…
  11. 11

    What is the difference between Dependency Inversion, dependency injection and a service locator?

    Dependency Inversion is a design rule: high-level code owns an abstraction and low-level code implements it, so the dependency points at the policy.

    // 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;}…
  12. 12

    Every Dart class is already an implicit interface. When is a separate abstract interface class with one Impl worth writing, and when is it ceremony?

    Write the interface where it marks a boundary that has to stay clean or is likely to change: a vendor SDK, a backend, a platform split, or a domain that must not import the data layer.

    // One concrete class: no ClockService plus ClockServiceImplclass Clock {  const Clock();  DateTime now() => DateTime.now();}…
  13. 13

    What do the Law of Demeter and tell-don't-ask say, and what is wrong with context.read<Session>().user!.cart.items.add(item)?

    Talk only to your direct collaborators, and tell them what to do instead of pulling out their data and deciding for them.

    // 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;…
  14. 14

    Find the bug: the settings screen throws LateInitializationError, but only when the app is opened from a push notification.

    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();}

    Prefs works only after init(), and only HomePage calls it.

  15. 15

    Which code smells do you see in this placeOrder call, and which refactoring fixes each one?

    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);

    A long parameter list, a data clump, primitive obsession and flag arguments.

  16. 16

    A utils.dart file has grown to 2,000 lines and every feature imports it. What is wrong with it, and where should its functions go?

    It has no cohesion: dates, prices, validators and dialog helpers share a file only because none of them had a home, and every feature now depends on all of them.

  17. 17

    Every repository extends a generic BaseRepository<T> with five CRUD methods. A year later half the overrides throw UnimplementedError. What went wrong?

    The abstraction was built from what the first repositories looked like, not from what their callers need, and every entity that did not fit was forced into a CRUD contract it could not keep.

    // 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()}…
  18. 18

    You are extracting a payments package used by three apps. How do you design its public API so the internals can change without a major release?

    Expose the smallest surface that covers the apps' use cases and hide every decision likely to change: the vendor SDK, the HTTP client, the storage.

    // 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…