Sometimes a C++ class grows too big and complex, dealing with too many issues, to the point where it is difficult to reason about it and test it. This article describes the problem.

The problem

The generic issue is lack of clear, single, focused responsibility for a C++ class. Let’s go through a few smells (indications that something is wrong) requiring refactoring to reduce dependencies.

Large files. For me a source file above 500 lines is becoming large. One example exception is very simple, very low complexity code (e.g. some giant switch mapping an enum to strings).

Another smell is a large number of member variables in a class, with no (or very loose) relationship to each other.

Another smell are classes called SomethingManager, SomethingAgent or similar that grew over time to contain all the code related to whatever Something is. They are symptoms of anthropomorphic design (to cover in a following post).

Related to the above is a large number of #include directives required to support the amount of functionality in the class. In general a file including more than about half a dozen other files indicates that its content tries to link too many things together at the same time. Examples of exceptions are:

  • Some entry point of the application where many objects are just linked together.
  • Would not count basic utilities like <boost/noncopyable.hpp>.
  • Would count a sequence including <vector>, <set>, <map> and <algorithm> like one (it’s using some standard containers).
  • Some API header like <windows.h> would include lots of other headers. Changing that might not be feasible for backward compatibility reasons.

Sometimes one can identify a group of member variables, that are used together in code, that are very loosely coupled with other groups of member variables of the same class.

I’m going to focus on the problem of a class depending/having too many member variables.

Sample problem code

Say we have a C++ program that deals with objects in a house. The entry point is a function called chillax. It performs the actions of a person that finishes a cup of tea, and then leaves the kitchen to go into the living room to watch TV. Between the rooms there is a door with knobs and a hinge that screeches. The TV is unplugged and has a remote.

Here’s how a fictional house class might look like:

house.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "tea.h"
#include "cup.h"
#include "hinge.h"
#include "knob.h"
#include "display.h"
#include "remote.h"

class house
{
public:
  void chillax();

private:
  void open_door();
  void close_door();

private:
  tea tea_;
  cup cup_;
  hinge hinge_;
  knob knob1_;
  knob knob2_;
  display display_;
  remote remote_;
};

house.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "house.h"

void house::chillax() {
  tea_.drink();
  cup_.rinse();

  open_door();
  close_door();

  // switch TV on
  display_.plug_in();
  remote_.press_button();
}

void house::open_door() {
  knob1_.turn();
  knob1_.push();
  hinge_.screech();
  knob1_.release();
}

void house::close_door() {
  knob2_.turn();
  knob2_.push();
  hinge_.screech();
  knob2_.release();
}

The class above is to a certain degree contrived because and its dependencies have already been abstracted into separate classes in order to keep the example short. Imagine an order of magnitude more code, members and includes.

Conclusion

The smells above are not constraint to C++. Instead of #include read require for Ruby, or import for Java and the arguments are pretty much the same.

Also one can apply the same logic outside classes e.g.: a program might perform unrelated actions depending on its command line arguments.