Avoid Comments

Avoid Comments

04 Jun 2018    

Yes, you read that right. Allow me to explain.

Code should be self explanatory. This means that the combination of your variable/class/function names, code structure and class relationships should automatically convey what’s going on without having to explain anything everything. In simple terms if someone else has to read a lot of your source code to find out what its doing, you’ve pretty much failed at conveying the exact purpose and should ideally think of renaming or splitting up the code. Of course, not having absolutely any comments is also ridiculous, so your comments should ideally only state WHY the code is there, not HOW is works. Time for some examples:

Often if you have comments that have to explain what the functions are doing it probably means you need to name them a bit better.

// Check validity then process
userLink->Process(data);

// Comparatively
userLink->ValidateAndProcess(data);

// Or even better, use phantom types - explained later on
auto const validatedData = userLink->Validate(invalidData); // function returns a locally tagged type
internalLink->Process(validatedData); // function only accepts the tagged type returned above

Constrain template parameters. If it is a complicated or a rather long SFINAE declaration, use a static_assert with a message for clarity.

// Only takes integers
template <typename T>
int PopCount(T val)

// Versus
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
int PopCount(T val))

// With concepts (C++ 20)
int PopCount (Integral val)

Similarly, there are also times when you have values or types that depend on some other part(s) of the code. It is best that a comment is not the only thing stopping someone from corrupting it. A combination of asserts and template constraints can be applied to link together disparate dependencies, such that changes to one causes an error in another.

If your comments basically state that a bunch of things should be valid for the code to succeed, that’s a good sign you’re in need for some asserts (static or dynamic).

void SomeFunc(Resource const* resource, std::size_t size, int num) {
    assert(resource && "Resource needs to be valid to retreive data");
    assert(size > 0); // In cases like this a message may not be necessary
    assert(num >= 0);

    // While it is possibly redundant doing this for every single argument in every
    // single function, a balance should be achieved to try and validate
    // code in debug & release appropriately
}

Phantom types, as mentioned previously, allow restricting the interface to accept validated types. This is done by using tagged templated structs.

template <class>
struct UserData {
  std::string Data;
  explicit UserData(std::string const& data)
    : Data(data)
  {}
};

struct Processed{};

std::optional<UserData<Processed>> Validate(std::string const& data);

// Avoid inadvertently using unvalidated/unsigned data
void ProcessData(UserData<Processed> const& data);

There is a small difference between comments and documentation. They should ideally be separate since they have different goals. Comments are meant for internal use, they should relay important & valid information in a succinct manner. Comments should be kept within source code and ideally should not bloat a file to the point where it is 70% irrelevant comments. Documentation on the other hand can convey a lot more information in addition to the comments from the code.

Lastly, if you’re every writing a comment or have written one, try and rethink if you actually need that comment and how you can work it into your code.

Further Reading: