Change is good

| 0 Comments

I like to experiment with new coding practices. Not too many at once mind you. I tend to add one new thing to how I work, operate in the new way for a while and then decide if the new way is worth adopting as a habit going forward. By adding new practices every so often I push my work habits in new directions; some of it is good, some is not so good, but all of it is an opportunity to learn. Sometimes during these experiments I try and go back to first principles; everyone says goto is bad, why is that, can it be good, etc.

This week's "new" thing is "reducing coupling by using an almost ridiculous level of header file granularity"; or, sticking typedefs in their own headers, most of the time...

Consider this interface:

class ILock
{
   public :
  
      virtual LockId GetId() const = 0;
  
      virtual LockIndex GetIndex() const = 0;
  
      virtual ~ILock() {}
};

LockId and LockIndex are typedefs. The concept of the lock's id and index are such that they're both simple scalar types. Where should these types be defined? The answer isn't as obvious as you might think and none of the solutions that I've come up with so far is "perfect".

These are the things I've tried so far along with their pros and cons...

1) No typedefs, use native types.
Pros: No coupling to the concept of (for example) LockIndex. No header file inclusion required.
Cons: No formalisation of the concept; no abstraction. All code that uses the return value of GetId() is implicitly coupled to the type used for the 'lock id' by all instances of ILock. Changing the 'lock id' from one intrinsic type to another is painful and error prone as the concept is duplicated all over the place...

2) Typedefs are defined in the same header as the class that 'owns' them.
Pros: The concept that the type represents is defined in one place. Changing the intrinsic type used is done in one place.
Cons: The users of the typedef are accidentally coupled to the other types in the header file; in many cases they would be coupled to these anyway, but often there's no need to be... For example, we may have a map that maps from LockIndex to ILock *. If the typedef is in the same header as the interface then we need to include the interface header to access the typedef and we could otherwise forward declare ILock.

3) The typedefs are nested inside the class.
Pros: The name can be shortened because the class scopes the type; LockIndex can become Index when it's defined within the ILock class.
Cons: Users of the nested type are now coupled to the type in which it's defined. This is like 2 only the coupling is even tighter. If you happen to want to store a vector of lock indices then the code that stores the vector is also tightly coupled to ILock interface; as is everything that uses it...

4) Each typedef lives in its own header.
Pros: Code is coupled to the concepts that it needs/wants to be an no others.
Cons: Lots of header files...

My progression through these solutions over the years has been along the lines of 2, 1, 3, 2, 4. I think my current preferred solution is almost a cross between 4 and 1. The proviso being that not every concept needs to have an explicit type; sometimes an intrinsic type is OK...

Leave a comment