Skip to content

Principles

JohnnyJayJay edited this page Apr 1, 2020 · 4 revisions

Principles and Idioms

Mela has a few core principles that guide its code structure and implementation. Of course, an entire framework can't ever be really perfect; feedback, criticism and suggestions are therefore always welcome.

Ahead of Time Computation

mela-command is generally very static in its nature. This does not conflict with the ideas of the bind framework - it's still able to adapt to your declarations and bindings. However the idea is that mela-command behaves in a deterministic way. The moment you start your application, the way mela-command will process your commands, groups and so on is set in stone. Additionally, all of the required setup is done in the beginning and before you use any affected component.

In other words, things are not decided at runtime when commands can be dispatched, but always before. Validation of your command registrations is done at the start and not when your application has already been running for hours or days. Groups are immutable after creation. All of this makes the framework very solid and robust, similar to Guice.

In short:

  • Think ahead of time. Avoid runtime registries and mutable objects (except for certain types of messages). Ensure that the important setup and initial computation is done before anything is actually up and running.

  • Validate as much as possible. Check all conditions that can be checked ahead of time and throw initialisation errors if something is not right. This enables quick fixes and precise error descriptions.

  • Encourage Guice integration. Do not force users to use Guice, but since this principle fits the dependency injection framework so well, you should make Guice components available if applicable.

Simplicity and Concision

mela-command's code base is much, much smaller than many of the frameworks it took inspiration from. The average number of lines of code in a core module file is less than 70. That is mainly because its APIs are simple and it does not lose itself in details. And still, it is extremely powerful and can accomplish most, if not all of the tasks similar frameworks can.

The reason for this is that the project has been revised and restructured several times in an entire year of early work, planning and experimenting. This resulted in its unique structure and implementation.

Simplicity and concision also involves sticking to object-oriented programming principles in general. Design patterns of any kind should be used if they contribute to this principle and don't lead to Java enterprise-like code with absurdly large and confusing APIs. Code does not need to be self-explanatory, but it should not be hard to understand after having familiarised oneself with its core ideas and structures.

In short:

  • Keep things simple. If you feel that something "gets out of hand", take a break, reconsider or even rewrite the feature, if appropriate.

  • Use flat hierarchies. Even though this is a Java project, complicated and big type hierarchies should be avoided. The focus should be on a more modern and simplistic code design.

  • Don't be lazy. Making implementations without thinking about the bigger picture is likely to have a negative impact on the entire code.

Extensibility and Openness

Traditional command frameworks are typically very extensible, not because they explicitely support extensions, but rather because they don't do all that much to begin with. mela-command follows a different idea of extensibility: APIs for all possible kinds of extensions should be provided. This means of course that some restrictions need to be enforced, but generally speaking, almost anything should be possible.

In detail, that means you need to consider each step where a user might expect or want different behaviour - and should seek to make this behaviour available or open for implementation. Furthermore, this also means that decoupled systems should be the ultimate goal. Things that do not inherently belong together should not directly depend on each other so that switching implementations becomes easy.

The smaller the decoupled systems, the better, because it implies that more code can be reused across different implementations. An example in the core module would be the GroupAssembler interface, which is applicable to any group implementation.

In short:

  • Find the middle between a restrictive framework and a lack of guidance. Allow as much freedom in features as possible, but don't leave users alone with their implementation either.

  • Follow the Dependency Inversion Principle. Use open interfaces rather than implementations to depend your code on. This allows for decoupling.

  • Consider possible variations or different directions of your implementations. Always try to design your code in a way that any user can fulfill their requirements if they are not met by the standard implementations.

Common Idioms

There are some common patterns that the entirety of mela-command sticks to for consistency:

  • Immutable instead of mutable classes. Most of the classes provided by mela-command are immutable. That ensures thread safety, predictability and smaller APIs.

  • Factory methods instead of constructors. Objects from the core and bind module are created using static factory methods rather than constructors. This adds a useful layer to the creation of objects.

  • Exceptions instead of 'status' return values. mela-command generally never uses return values to indicate whether something has gone right or wrong. It embraces the usage of exceptions instead

  • Optionals instead of nullable return values. In cases where a value may not be present, mela-command returns empty Optionals rather than null.