Superclasses indicate generalities, abstractions. Try to clarify the intent of the abstraction as much as possible in naming. The highest level abstractions should consist of a single word phrase. For instance, Task is okay, Acquisition_Task is not a good root since Acquisition is a qualification of Task. A QualifiedSubclassName should not be the root of a hierarchy. See SystemOfNames.
How does this relate to PrematureGeneralization? Using Acquisition_Task as the root suggests that more generalisation/abstraction is possible but we've chosen not to represent it yet. Won't this proposal force people to deal with the generalisation early? -- DaveHarris
That makes sense, and I see a flip side also. If we start with Acquisition_Task we are probably thinking about generalization too early even if we are not doing it.
I guess that it all depends on the code impact of adding a new base class and name (Task from Acquisition_Task) versus adding a new derived name (Acquisition_Task from Task) and migrating code from the base to the derived. I know that in C++ it is easier to do the former. Less work.
Interesting conflict. This leads to a thought that I'll add to PrematureGeneralization. -- MichaelFeathers
You need to start with a SystemMetaphor. From that, single class names for your superclasses should be obvious
Yes. In the project which used Task and Acquisition_Task, Task was a metaphor class. Funny enough, it ended up in the middle of the hierarchy. Here is one line in the hierarchy from supermost to submost: Thread, Work_Thread, Task, Connectable_Task, Asynchronous_Task, Acquisition_Task.
Everything prior to Task was late generalization and it shows by the names. Thankfully, I didn't actually do what I mentioned in QualifiedSubclassName: make laundry list names of compounded qualifiers. I've just had that style on the brain because of some propeller heading I've been doing. Apologies.
It does seem, however, that it is easier to generalize later than to take a class implementation that is more specialized than its name, make a subclass and then push the implementation down into the subclass. In the hierarchy used above, something like the following once happened. Imagine that everything in Acquisition_Task was in Asynchronous_Task because we did not want to subclass right away. At the point at which we discover our implementation is more specialized than the name and then subclass, we bite some big changes. Type or interface references scattered in the code point to an implementation that is just a skeleton of what was there before and perhaps no longer a correct because of that. -- MichaelFeathers
That would be an example of refactoring. It can look intimidating but it often turns out to be cheaper than you might expect. Maybe half an afternoon to grab all the files, make the changes and put them all back (assuming traditional locking VCS). It's probably Ok to make everything use the new subclass (ie Acquisition_Task) since that restores the status quo. Later on you may change some references back to use the base class (ie Asynchronous_Task) again, but that is a separate operation and can be deferred until its needed. If you have better tools, it'd be quicker. It's arguably better to incur this refactoring cost now than to continue with the wrong names, or to try to anticipate the changes earlier on. This being the whole YouArentGonnaNeedIt debate -- DaveHarris