Skip to content

Development philosophy

Tyler Mandry edited this page Jan 15, 2016 · 2 revisions

Emphasis is on stability, model consistency, and correctness of the API. Together with a simple, type safe API, these reduce the possibility of errors while making it easy to create a powerful window manager.

  • Stability: Don’t crash, unless one of our fundamental assumptions about the underlying system has been violated (if we can recover, then log and throw error instead).
  • Model consistency: Stay self-consistent at all times, so there aren’t hard-to-debug errors in applications that use Swindler. Also keep our model consistent with the system, getting up to speed as quickly as possible without breaking self-consistency.
    • Self-consistency means, for instance, that if an event is emitted, the data throughout the entire model is already updated to reflect the change, and that if window != window.application.mainWindow, then window.isMain == false for all windows. No exceptions.
  • Correctness of the API: Strive to document the actual state of the accessibility API as it is implemented in OS X applications and as it relates to window management. From our API, you can tell that you can always assume windows will have a size, for instance, but not that an application will always have a mainWindow.
    • Reduce surface area of possible errors by being exhaustive in our own error handling and documenting exactly which errors need to be handled.
    • Type safety, which the AXUIElement APIs do not have, is another component of this.

Rules

Whenever possible, always rely on the Swift type system.

Even though it’s missing features and can be a pain to wrestle with, by piping type information through any way we can, we eliminate the possibility of many programming errors and unexpected runtime failures. Only use as! and as? as a last resort.

User code should never have to make a ! assertion because of this library, and they should also be avoided internally, when possible.

Design runtime failures to happen at startup.

Any time you cannot rely on the type system or other static checks for failures, design possible failure modes so they fail during startup time while the WM is being configured, not as a result of some outside event.

Testing

Test everything that can go wrong. This is crucial to the stability of the framework. Test unexpected error states, unless they are so unlikely to be possible that it’s acceptable to crash in that situation.

One of the toughest (and most important) things to test is race conditions. I’ve created a number of helper “adversary” classes to aid in this, and there are plenty of examples of tests using them.

The code is designed to allow injection at multiple levels. Sometimes it’s appropriate to test a class along with its actual delegate (e.g. testing Property using a real AXPropertyDelegate, on fake UIElements), because testing them separately could miss certain bugs in their interaction. Having a large number of true unit tests is nice, but we must have confidence that all the code works, and often that means testing interactions is required.