A companion discussion page for ExtremeGuidelinesForCeePlusPlus.
Michael Feathers on how much to insulate
I also have the following process issue, which could become a page in itself:
Where is the tipping point? One part of me thinks that you should start with the full treatment:
I would suggest one class or four or more classes. If there is only a single derivation of a class, don't create an interface or class factory. Once there are at least two derivations, create the common interface, a class factory, and the derived classes.
Another part of me says (and this is what I lean to and have done piecemeal):
Don't be afraid to move classes around to make your libs smaller. Doing template closures on large libs consumes much more time than you'd expect. Keep all your lib directories at the same level - don't build deep package hierarchies - so that it's easier to move classes around. I strongly recommend the use of an automated build environment like Makeit (http://www.dscpl.com.au) instead of the godawful by-hand settings in a crap tool like VS6. Many C++ developers have been spoiled rotten by IDEs ... when the IDEs don't do what you need, they still resist anything that isn't built-in. Screw 'em - make an auto-build environment and tell 'em to configure their IDEs to invoke it. Then it's their problem. -- PeterMerel
After you have your header files and modules well organized (approximately one class per header file, restrained inclusion of unnecessary header files from within headers), and you have a working compile-time dependency system (i.e. "make depend"), and you have your dynamically-linked shared libraries in place, stop worrying about compile-time overhead. Sure, it's not an interpreter, but that's half the point. The debugger is your interpreter, and the place to try quick inner-loop repetitions. A reasonable debugger will reload only the class libraries that have changed since the last run. Careful layering of code into ever-more specific class libraries might be key. I work with .5 MLOC of code divided into 50 libraries (spread over four vertically stacked source trees), and I'm very comfortable practicing my own variant of ExtremeProgramming. And at times it has scaled to teams of 3 or even 4 people :-). -- ScottJohnston
Only do what you need to when you need to do it. That's the XP way and it works for C++. The "tipping point" is different for different classes and often for different methods. Assume YouArentGonnaNeedIt and refactor as you go. This much of XP, at least, I have applied successfully to my own work. -- PhilGoodwin
Discussion about a starting point for future refactoring
Peter, responding to all your comments at once. I tend to agree, and I know that what you suggest is very good advice; stuff I've done. The thing that I'm trying to figure out is whether C++ programming has to be so inconsistent. What if I pull all functions into the .cpp files? Would I feel more comfortable changing things that would have been inlines? What if I avoided value passing and stack objects at all costs? Everything goes off the heap and you only have to forward declare in the header. When performance suffers, you pool objects or move back to values. I admit that this sounds crazy, even to me, but it is one extreme and I wrote it up to be controversial. The other, is to take it incremental, like the second bullet list.
What I'd like to see, is a way in C++ to say: "do it this way, all the time, and we will measure and move in less simple directions as we need to." The rub is that the initial condition needs to allow good migration to the more specialized forms. I hope that the second bullet list moves in that direction. We need to be able to develop criteria for refactoring the physical design as well as the logical design. It probably requires experimentation.
The very fact that C++ offers all these options and forces us to decide among them, makes me wonder about AlternativesToCeePlusPlus. But when I shake myself out of that, I wonder whether we can make set of patterns which allow us to deal with physical refactoring comfortably, migrating a code base back and forth across a continuum of well understood forms that allow us to be very open to logical refactoring at times, and very closed for speedy compilation at others. Sometimes an airplane needs its gear dropped; other times it needs it retracted.
-- MichaelFeathers (has been programming C++ for years and is re-examining a lot)
I understand your concern here Michael only too well. In fact it's this in chief that leads me to despair for C++ on AllPanaceasBecomePoison. You see I don't think it can properly be done. The best I can recommend is to stick to StlStyle - I believe that provides us with an unquestionably core dialect from which we need vary only very occasionally. But this isn't a complete answer.
The StlStyle page is somewhat weak. Can you add a bit more elaboration please? -- PhilGoodwin
Establishing dialect standards
One thing that's probably more important in C++ than any other language that might be amenable to XP is to establish dialect standards even before project standards. A good basis for this imho is the Ellemtel standard, now simplified and morphed into IndustrialStrengthCeePlusPlus. Taking this and STL as a basis provides you with a good starting point.
Such a point ought to be very useful to someone looking for ExtremeGuidelinesForCeePlusPlus. Not so useful for an old C++ hand who probably needs to define a standard himself. But apart from we small band of brothers how many of them are there in the world? -- PeterMerel
Yeah, I have many doubts also, but I'd like to give it a crack. If there turns out that there is no way of simplifying this decision process, and moving back and forth between nimble and rapidly compiled code, then Ron has all the more ammunition.
Michael Feathers -- Starting Points for Reducing Compilation Cost
Starting from base principles.. the simplest thing that could possibly work.. and some of Phil and Peter's ideas, I've come up with a set of proposals for XP in C++. They err on the side of reducing compilation cost, because we all know that if you don't have much of it, it is easier to increase it later (for secondary goals of course). :-).
Please let me know where these initial conditions could cause a moderate sized project to go through grief:
Downsides: Upfront work. Overuse of heap could push errors, but this can be ameliorated. Overuse of heap could cause performance to suffer, but with compilation costs down, you can move to values or pools more easily.
Compilation Cost Comments
Mike, apologies if I seem harsh below - this might work for you, but I could never work this way.
Actually, adding ConstCorrectness after the fact to a well-designed program is blazing fast, mostly limited by compilation and typing speed.
I added a paragraph about mixins that directly contradicts the preceding paragraph on mixins - please feel free to delete it to restore consistency, but if you do, please also rename the page to ARandomAssortmentOfCplusplusPreferences. -- AndersMunch
Reference Counting
Ref-counting is mentioned a couple of places above, but never as the main point. Now that I think about it, that seems like a major oversight. In my opinion memory management deserves at least one bullet point. For example:
''ReferenceCounting is one of a dozen or so idioms that no C++ programmer should be without. But there are many projects where it isn't needed at all. there are bunches of resource management techniques and the correct one needs to be chosen for the given situation. On nice thing about C++ is that it doesn't pretend that memory management is the only resource management problem there is. I really miss having destructors that get called at well-determined times in Java for instance.
There are two questions here: Should you use ref-counting, and should you use smart pointers. I'd argue that there is a real benefit to using smart pointers whether you're going to ref-count or not: They're more exception friendly and they're more STL friendly. And if you're going to go to the trouble of using smart pointers, you might as well make them ref-count. Another way to look at is this: If you don't need smart pointers in your project, what is the cost if you use them anyway? (I'd argue it's negligible). -- CB
--- Limiting The Number of Language Features
In the spirit of DoTheSimplestThingThatCouldPossiblyWork, it might be interesting to get a book on C++ language circa 1989, and stick to the oldest language constructs until you really understand what you need of the new ones. There are a small number of semantic differences since then, but the language is for the most part backward compatible, and newer compilers will warn you when an old construct has been deprecated. You can live without features like exceptions, templates, dynamic casting, and even multiple-inheritance - all things that addressed perceived needs in the language, but also seem to have impacted its ease of use and degree of programmer satisfaction.
On the other hand, I would guess the C++ Standard Template Library gives you no choice but to immerse yourself in the latest language features. Unless you can find an alternate collection of common re-usable objects. NIHCL had Smalltalk appeal. I rely on the UnidrawFramework for all my lists, hash-tables, trees, ref-counting, etc. -- ScottJohnston
The newer language features really help... a lot. Ignore them at your (and those who follow) peril!! STL is a huge win. The NIHCL experiment proved that Smalltalk style inheritance doesn't work in C++. In fact, STL proves that inheritance isn't needed at all for reuse, templates work extremely well. In one library I did, the use of exceptions and autoptr halved the amount of code I had to write (I refactored about half way through). The biggest problem that I see with C++ is that it's very complex and many programmers don't bother to learn most of the features of the language... features that can actually simplify development and result in cleaner more usable code. The worst part about C++ for me is inheriting something from one of these guys/gals. I cannot say I've always liked C++, but I do (finally) respect it. -- lef
See RefactoringCppToReduceDependencies
Design Up Front??
Why did DesignUpFront creep into the ExtremeGuidelinesForCeePlusPlus? Maybe it is not DesignUpFront, but BuildInfrastructureBeforeApplications?. I haven't found this necessarily to be true, and I've found that most infrastructure bootstrappers relied on parallel development of at least sample applications to guide them (and took an evolutionary approach to creating their infrastructures as well). Isn't the reason people desired a static C++ infrastructure because they were not familiar with the balance of ExtremeGuidelinesForCeePlusPlus? For another take on evolving C++ base classes in parallel with derived classes, see [1]. -- ScottJohnston
I echo your concern Scott. I'd prefer to keep compilation costs down by whatever means possible and see if infrastructure motion becomes an issue, but I'm open to the possibility that it may not be possible. What I do know is that I haven't been ruthless enough about cutting down CostOfChange in C++ to see what the outer limits are. I'd like to try development in ExtremeFormsForCppCode to see just how low the CostOfChange can be. -- MichaelFeathers
Whether you call it DesignUpFront or BuildInfrastructureBeforeApplications?, there's clearly a substantial amount of YouAreGonnaNeedIt operating here. That may be OK; all these suggestions weren't just pulled out of peoples' hats - they're based on experience. In the Smalltalk world, YouArentGonnaNeedIt may apply close to 100% of the time, whereas in the C++ world, it may only apply 90% of the time. Lots of practical experience suggests that that 10% can cause you a great deal of pain that you could have avoided. I think the idea of using sample applications to guide infrastructure implementation is an excellent one, too. I look at this as not just testing your code, but also testing your design. In an Extreme process one way to approach this would be to choose the initial UserStories based on what's best for bootstrapping the infrastructure, and only allowing the users to prioritize in later iterations. -- CurtisBartley
I agree some amount of pre-existing infrastructure is needed for any successful project. And arriving at an initial collection of infrastructure takes more diligence in C++ than other object-oriented languages. But I see no reason why that infrastructure has to be static over the course of the project. Stop building infrastructure seems to suggest this. But then the first line says If you're doing XP you probably don't need this so maybe my point is moot.
There is a deeper issue here, about XP and common infrastructure that doesn't necessarily arise in the Smalltalk world. I've uncovered two viewpoints on this in the last year. Extremists say to ignore the design of the infrastructure (or application framework or reusable software) and focus on the immediate use case. Designists advocate a static and unchanging infrastructure. Both seem to unnecessarily constrain the programmer's way. -- ScottJohnston
Yes, I'd like to offer a middle ground between the two. Which is to not build infrastructure until you need to, but to then do a fairly thorough job of it. For instance, if you find that you need a class to translate between different string formats then predict which string formats you'll need to handle and deal with them all right from the start. The reason for this is that you'll likely be using this class all over the project and you don't want to have to recompile everything in the world just because you want to add some incremental improvement to your string translation class. It's a matter of balancing the risk of having to recompile and retest some amount of code against the risk of writing code that you'll end up never needing. There isn't a fixed rule that will make these decisions for you - you'll have to pay attention and think. -- PhilGoodwin
I agree there is value to a little forward thinking when laying down base classes, and that it is a highly subjective endeavour. And I'm willing to balance the risk of recompilation/retesting against having unused methods, because I see neither of them as risk factors worth worrying about. I get satisfaction from recompiling and retesting code, having once again verified the quality of the system, and perhaps removed a few more problems that went unnoticed at an earlier date. Unused methods have little impact on project success either, and can serve as reminders at a later date of the original base-class implementors intent. See also: RefactorSlack. -- ScottJohnston
C++ isn't XP Unfriendly in the first place
Some is made here, and more is made on a couple of other pages, about special techniques for running XP in C++. MichaelHill is currently running XP in C++ across a modest-sized project with four developers (200 classes, maybe 20 kloc or so). They are experiencing zero need for special C++ constructions. They have a fairly tight set of coding and naming conventions, but then, that's called for by XP already. We need a more thorough discussion of the fundamental problems that are not solved via (I hate to say this) 'normal' XP methods.
In particular, a distinction should be made between large, moderate, and small projects. Much of what appears on the source page seems potentially relevant to large-scale projects, but to a twenty-thousand line project it seems like a truly massive assertion of YouAreGonnaNeedIt. Perhaps it would be a good idea to rank our suggestions according to project size starting with smaller projects and adding more suggestions for larger projects on the assumption that they will still need the stuff from smaller projects. It's likely that the stuff from the smaller projects will be useful to all projects, not just XP projects.
CurtisBartley has worked on a couple of C++ projects on the order of 10 or 20 kloc of code and no more than a couple of people and didn't need to resort to anything special in order to get good turn-around times during development.
It is somewhat ironic that smalltalk XPers talk about not creating infrastructure when they are the biggest users of premade infrastrucure: the smalltalk library and environment. A large percentage of the "infrastructure" we make in C++ is just to get to the level of the smalltalk environment.
On larger projects it is far more efficient to standardize on an approach to things like: OS calls, GUI calls, task encapsulations, debug and log libraries, data structure libraries, messaging infrastructures, makefiles, build system, bug tracking system, source code control system, memory management libraries, etc. All this stuff is infrastructure and it would be madness to let each developer develop her own version of each.
''Don't take my comments on infrastructure to mean there should be none, or that its development shouldn't be centralized, organized, well-thought out. And don't assume because I still think infrastructure should remain mutable over the course of the project that I'm drawing my conclusions based on Smalltalk. I've been working with nothing but large scale C++ frameworks for the past ten years.
It's not that there should be no infrastructure or that it shouldn't be centralized, organized and well thought out. It's just that it must remain mutable over the course of the project. XP may be the first written-down methodology that gives permission to evolve and refactor where ever you need to, when ever you need to. Teams thrive on their ability to change things at all levels as new requirements arise. It is hard to imagine how you arrive at a healthy hierarchy of object-oriented code without doing this. Although, there is usually a distinction in practice between those who evolve base classes and those who evolve application specific classes. At least on C++ projects.
Contributors: MichaelHill CurtisBartley ToddHoff ScottJohnston PhilGoodwin
My experience, working on C++ projects with up to 200kloc, and up to 10 developers (I'm currently the sole developer on a project with over 100kloc of C++) is that turn-around time and infrastructure are not big issues. If you need find a need for infrastructure, you build it, and refactor the existing code to use it. Admittedly, it can be a pain if the current code is poorly factored, but then that's the payback for not keeping your code tidy, and you can still take SmallSteps, and RefactorLowHangingFruit first. -- AnthonyWilliams
Use LawOfDemeter
The things that lead to slow recompiles are things which lead to bad design anyway. Normal good design, with minimal inter-module dependencies, should be enough in most circumstances.
For instance avoid:
x = a.b().c().d().e().x();because it requires the calling code to #include b.h, c.h, d.h and e.h. Instead put an x() function in class A, and isolate the dependencies in a.cpp.
But this is good design anyway, because the path through the data structure is a fragile thing.
Contributors: DaveHarris, PhilGoodwin
Don't use the dot operator
I often use the pimpl idiom, either to make a ReferenceObject or as compilation firewall. Since the dot operator can't be overloaded, this means either forwarding all method invocations, like so:
class Cow_impl { public: ... void moo() const; void chew(Grass&); }; class Cow { SmartPtr?<Cow_impl> impl; public: ... void moo() const {impl->moo();} void chew(Grass& grass) {impl->chew(grass);} };or overloading the arrow operator -> and changing all invocations of cow.moo() in your code to cow->moo().
Neither option is all that fun after you've done it a couple of hundred times. Therefore, you should overload the arrow operator to begin with for all user defined types and never use the dot operator for member access.
Note: This is ugly. For every user defined type you will almost always have two extra methods:
class MyClass { public: MyClass* operator -> (); const MyClass* operator -> () const; ... };There is something about this that doesn't look right and that's enough for me to say that it shouldn't always be done.
It smells like a template mixin waiting to happen -- DanilSuits
// WARNING: uncompiled code template< typename T > class ArrowOp? { public: T* operator -> (); const T* operator -> () const; }; class MyClass : public ArrowOp?<MyClass> { ... };
Start with all methods class-inline
I assume that this refers to classes which are isolated in an implementation file - if we are inlining in the header, that's likely to force increased compilation times -- DanilSuits
Disagree. Putting methods inline increases coupling. If one keeps methods separate from the header, one can use forward references rather than #includes. As soon as one tries to perform an operation on another class, the forward reference is no longer sufficient. -- WayneMack
Refactored this page. Made it smaller and converted some thread mode stuff to multi contributor. I would like to know more about WikiRefactoring from VolunteerHousekeepers. -- PhilGoodwin