vapor-docs/3.0/docs/concepts/code-contributions.md

3.8 KiB

API Design

For contributing code we have guidelines that we strive for, and requirements. Accepted code must comply to all requirements and should follow all guidelines.

Requirements

The requirements stated here must be followed for all Vapor 3 code starting with the beta. We designed all of Vapor 3's APIs (including the Async library) to require little to no breaking changes over the coming years.

Enums

enums should only be used where adding a new case should result in a breaking change. Specifically since exhaustively switching on an enum will no longer compile when a new case is added. For things like errors, this is obviously undesirable as supporting a new error type results in code no longer compiling. However, for something like a supported data type, enums make sense because the errors help you track down all of places that need support for that new type added. Most use cases for enums are when the enum is internal to the current module.

Classes

Always mark classes as final. If you plan on using a public class, look into a protocol or generics oriented approach.

Low-level APIs

Low level APIs such as sockets, SSL, cryptography and HTTP should be oriented towards simplicity, maintainability and correctness. If you feel an API becomes more complex for end-users, you can add high level APIs that rely on the low-level ones.

Tests

The unit tests must have a minimum of 80% code coverage.

Uniformity

Stick to the familiar API patterns. If connecting a socket has the following signature:

try socket.connect(to: "example.com", port: 80)

Copy the signature, rather than adding an extra case. If you need more metadata for connecting, consider setting them in the initializer or as a variable on the type.

Binary data

Binary data should be passed around in 3 formats;

  • ByteBuffer
  • Data
  • [UInt8]

You should use ByteBuffer when working with Streams to limit copies and improve the stream chaining applicability. Data for larger sets of data (>1000 bytes) and [UInt8] for smaller data sets (<=1000 bytes).

Dictionaries and Arrays

Where you'd normally use a dictionary (such as HTTP headers) you should create a separate struct instead. This improves the freedom for future optimizations and keeps the implementation separate from the end user API. This results in better future-proofing and helps building better (more specialized) APIs.

Guidelines

Performance

Performance regression is acceptable to some degree but should be limited. Here are some tips on achieving high performance code or ensuring more performance can be added in the future.

Access modifiers

Try to use the public keyword as little as possible. More APIs adds more complexity than freedom. Specialized use cases are always free to build their own modules, and a public keyword can be added but not undone.

Comments

Green is the future, and we believe in that, too! I'm not talking about green energy, but the often green coloured code comments. Code comments important for the users of an API. Do not add meaningless comments, though.

Documentation

Every public function, type and variable should have a link to the documentation describing it's use case(s) with examples.

Argument labels

Along with uniformity described above, you should try to keep argument labels short and descriptive.

Argument count

We strive for functions with a maximum of 3 parameters, although certain use cases permit for more arguments. Often called functions should strive for less arguments.

Initializer complexity

Initializers are complex enough, it is recommended to not put optional arguments in an initializer since they can be modified on the entity itself. Try to limit the initializer to what really matters, and put the rest in functions.