Skip to content

Latest commit

 

History

History
150 lines (104 loc) · 3.79 KB

style-guide.md

File metadata and controls

150 lines (104 loc) · 3.79 KB

Lasso style guide

Some general notes about writing idiomatic Lasso code.

Contents:

Naming conventions

When conforming to a Lasso Module (ScreenModule, StoreModule, FlowModule), use the module type in your module name:

enum MyLoginScreenModule: ScreenModule { ... }
enum CrazyPeopleStoreModule: StoreModule { ... }
enum CoolFeatureFlowModule: FlowModule { ... }

When declaring related types (i.e. view controllers, stores), just use the prefix from the module definition, without the extra module type:

final class MyLoginStore: LassoStore<MyFancyScreenModule> { ... }
final class MyLoginViewController: UIViewController, LassoView { ... }
final class CrazyPeopleStore: LassoStore<CrazyPeopleStoreModule> { ... }

Flow subclasses should include Flow in their name:

final class CoolFeatureFlow: Flow<CoolFeatureFlowModule> { ... }

Module definitions

Try to limit module definitions to type declarations.

When defining your ScreenModule, ViewModule, and FlowModule enums, try to limit the code to just declaring new types. If you need to add functionality (e.g., convenience initializers, etc.), it's better to add these in extensions so that readers can more easily get the big picture of the module. This allows for easy scanning of the module definition, to quickly get a clear picture of all the module's types.

For example, consider a module definition that mixes type declarations with conveniences and functionality:

enum MyModule: ScreenModule {
  
  struct State: Equatable {
    let name: String
    var value: Int
    var canSubmit: Bool
    var error: Error?
    
    enum Error: Swift.Error {
      case badInput
      case unknown
      
      var description: String {
        switch self {
          case .badInput: return "invalid input"
          case .unknown: return "unknown error"
        }
      }
    }
    
    init(name: String = "", value: Int = 1) {
      self.name = name
      self.value = value
    }
    
    func updateCanSubmit() {
      canSubmit = !name.isEmpty && value > 1
    }
  }
  
}

By moving those conveniences and helpers into extensions outside of the module definition, it becomes easier to read

enum MyModule: ScreenModule {
  
  struct State: Equatable {
    let name: String
    var value: Int
    var canSubmit: Bool
    var error: Error?
    
    enum Error: Swift.Error {
      case badInput
      case unknown
    }
  }
  
}

extension MyModule.State.Error {
  
  var description: String {
    switch self {
      case .badInput: return "invalid input"
      case .unknown: return "unknown error"
    }
  }
  
}

extension MyModule.State {
  
    init(name: String = "", value: Int = 1) {
      self.name = name
      self.value = value
    }
    
    func updateCanSubmit() {
      canSubmit = !name.isEmpty && value > 1
    }
  
}

switch case spacing

Precede each switch case statement with an empty line.

Lasso replaces delegate-style protocols with Action and Output enums. This allows for precise and flexible connections between types, but also necessitates writing lots of switch statements, with potentially non-trivial code per case. For readability purposes, it's a best practice in Lasso to have a single empty line preceding each case statement - just like it's best to have an empty line preceding each function:

override func handleAction(_ action: Action) {
  switch action {
  
  case .didSelectItem(let idx):
    guard idx >= 0, idx < state.items.count else { return }
    dispatchOutput(.didSelectItem(state.items[idx]))
  
  case .didAcknowledgeError:
    update { state in
      state.phase = .idle
    }
  }
}