5

I know that there are some other questions about C++ refactoring, but none satisfies my needs.

I have a background in Java and Python programming, but now I'm approaching C++. I've read (and absolutely love) books about agile coding, test-driven development (TDD), refactoring, etc. The first that comes in my mind is "Clean Code". From this I have learned a lot of things: keep my functions short, keep my classes short, keep names of variables/classes consistent, etc..

I like very much this way of working: first resolve the problem, then refactor in small functions (and one function does only one thing), adding subclasses, etc.

I strongly believe that this way of working is made easy by modern IDEs: add file, rename function, rename classes, etc..

I'm now coding in C++ with CMake inside Qt Creator. All of those things are more difficult. Even adding a file is "more difficult" (I have to generate one header file, one source file, add them to cmakelist.txt, run again cmake, etc.). Thinking about renaming files (I have to manually change all includes) or some strong refactoring methods that in Java with Eclipse has no cost now gives me a headache.

I mean.. it is not "difficult" in the meaning that are "hard task", but the human mind is kind of lazy: if I can do other easiest way..

In Java with some modern IDE like Eclipse.. refactor is the fun part; some clicks and voilà, it is done. I do it every time I pass a test. It is easy, fun, and I like reading my clean code with small functions and nice names.

Now everything is more difficult mainly because there are no tools that can do it without cost. I bare myself in bad attitudes driven by lazyness: write several classes in a single super-long .h file because I don't want to create new files, spent hours in modifying classes/functions/variables with more appropriate names and manually change all includes, write long functions because when I automatically change a function's attributes that concerns the std lib Qt Creator is deleting the templates so I avoid it, and many others..

This is quite frustrating.. What can you advice me? Do I have to get more relaxed in the "rules" of writing clean code? Do I have to does not think about this stuff and thinking only in getting things works? Or (maybe this is the more importart question) do I have to be thinking about some other way of working? No more incremental/iterative/agile way, but more waterfall and spend more time in designing stuff before coding in the hope of minimize the refactoring when the tests are passed?

Anyway, any comment are welcome because I am very far from being a good programmer : )

9
  • 1
    It sounds like you think clean code means short code. That's not necessarily what clean code is all about. The ceremony you are experiencing in C++ is there for a reason; if you don't like that amount of detail, you might be happier in a language like Python. Jun 5, 2013 at 15:49
  • No it is not, sorry if I have not well expressed my feeling. I think that clean code is the outcome/consequence of an hard work of refactor/refining.
    – nkint
    Jun 5, 2013 at 15:54
  • @RobertHarvey Unless he wants to learn C++ or have specific reasons (specific project need?) to learn C++. In which case he will learn a lot. Even about other languages.
    – Klaim
    Jun 5, 2013 at 15:54
  • 2
    @Klaim: I'm not disputing that, but despite some programmer claims that hubris and laziness are a programmer's ideal traits, in fact it is persistence and attention to detail that will win the day. I came from a Pascal background and currently use C#, both of which require less detail than C++, so I speak from experience. Jun 5, 2013 at 15:57
  • 1
    Kind of makes a good case for doing diligent design up front =) Just because agile-like methodologies let you dig 6 foot deep holes at each step to bury yourself in doesn't mean that you should run around digging holes before surveying the land and seeing whether you need holes in the first place. Aug 21, 2013 at 17:33

2 Answers 2

13

TL;DR: C++ has an old compilation model and hard-to-process syntax (because it is highly contextual) which makes tools hard to make, but they exists. Refactoring is a bit harder to do in C++, but it's just a bit slower to do than in other languages.

I think you just need some additional information about C++ itself and related tools. Let's see:

  1. There ARE refactoring tools for C++ that would help in renaming files, etc. One example is the Visual Studio plugin Visual Assist X. I think Eclipse and other editors do have some refactoring tools too. They are imperfect, but they have been very useful for me for years. Now, where it is less useful is when you don't organize your code in a way which is usual and "forced" in other languages like Java or Python or C# because...

  2. C++ suffer a lot from using a very old compilation model. It makes some kind of code (header-heavy) slower to compile, it makes hard to locate library headers, etc. The compilation have to be understood to fully understand why depending on the kind of library you use or develop, you will not organize your sources directories the same ways. Anyway, this makes tools hard to build around it. In particular because macros are the way we import modules, which means it's all copy-pasting.

  3. Defining header + source is classic if you are defining most class systems, but it's far from being the normal/only way of doing things in C++. For example, a lot of Boost C++ libraries are header-only which mean there is no associated cpp file and you can't test the code without adding cpp files to compile it (headers don't really exists for the compiler, see the compilation model).

  4. Also, C++ syntax is highly contextual which means a tool that is able to refactor C++ correctly should have the same kind of information about the processed source code as a compiler. That is one reason for the efforts of LLVM to provide libraries to parse and have a correct model of C++ code, which can then be used by tools implementations. However, only a few tools so far are usable, but we expect a lot of refactoring tools to emerge from the LLVM effort in the coming years.

  5. Having a modern, structured and well-defined module system like in Java, C#, Python and Ruby is clearly superior for compilation speed, but more importantly to help the compiler know what is a module and how modules are linked together. The C++ ISO standard committee is working on such a modern module system to be added into the C++17 version if it is ready then. The point is that the issue is acknowledged and being solved. The work on this is being done into the LLVM/Clang compiler.

  6. Beware that CMake is an meta-build-system. What I mean here is that it's independent of the edition tool you use (I use it with Visual Studio for example) and add, indeed, an indirection into the process of adding/removing/renaming file. As C++ don't have any standard build system, it is one such a tool, but it is a meta description of your project. For example, if I directly use Visual Studio or QtCreator default project formats, it is far easier to change file names as you can do it directly into the editor. But indeed these project files are not compatible with other editors/platforms, which is why you use CMake to be sure you will be able to generate project files for a variety of development setups. Basically, if you don't use CMake, refactoring is faster, but you loose cross-editor/cross-platform build-system features.

All that being said, my experience so far with C++ refactoring is that if you have something like VAssistX it is enough to do most of it. Renaming files indeed takes longer to do correctly than in other languages, but it's not that long. That being said, it would be helpful if any change to a C++ project would be faster to check, which is why a module system is really necessary. Hopefully you don't work yet with code that takes an hour or more to compile, in which case the impact on refactoring is devastating as people don't want to modify too much the code, because it's making them insane to have to wait. This is a hard problem for the C++ community and again work is in progress to fix it. But it will take years.

4
  • 1
    Lately we have CLang that can be used to all kind of tricks related to source code, if one is not happy with the existing high-level tools.
    – Balog Pal
    Jun 5, 2013 at 16:07
  • If the project builds long time that is the first thing to fix... Using precompiled headers and multicore parallelism you can keep full build in minute range. (just avoid the 1 file for every dozen lines school from last century inspired by exclusive-locking source control)
    – Balog Pal
    Jun 5, 2013 at 16:09
  • @BalogPal About Clang, that's why I'm pointing by making references to LLVM (which are the libraries Clang is made of). About build time, there are big limits to what you can do to reduce it. Experience shows that good code organization beat precompiled headers which actually often makes build times longer OR have not much benefit - but it depends on your case and how you use it. Also, linking time is LONG and can't be fixed by parallelizing compilation. Other efforts points to a change in the compilation model. Work on a massive project makes things worse.
    – Klaim
    Jun 5, 2013 at 16:25
  • 1
    After years of having to maintain legacy c++ software in Visual Studio 6.0 I pretty much can't imagine ever writing c++ code again without Visual Assist X. Jun 6, 2013 at 1:37
8

Now everything is more difficult mainly because there is no tools that can do it without cost. I bare myself in bad attitudes driven by lazyness: write several classes in a single super-long .h file because I don't want to create new files,

Let me just reflect on this. It keeps popping up and I remain baffled. Why people are obsessed with packaging things in separate files. Here in 2013? Why putting more than one classes in a header considered lazy or bad?

And while for headers I could find some excuses but same applies to implementation files too. I ask why and a common answer I dread is "so I find my stuff easily". And demo looks like he summons the solution explorer, spends a minute closing irrelevant branches, thinks another minute which project it will be, then locates it on three, inside tries different subbranches, and eventually searches (using eye) the file matching the class name from a list of 50 similar names. Finally opening it with a small hooray. While I want to shoot myself six times in the back and jump in the river.

While we have VA-X "open file in project" and the way better "locate symbol", and 1-click navigation, or as last resort the old-style find-in-files, all could produce anything from anywhere in seconds. And even with the just described WTF method it would be way faster to find the thing if all the 50 files were just concatenated to one.

Those who fiddle with build with C++ know well that the time is quite proportional with the number of .cpp files while length of a file is irrelevant. 50 classes put in 50 files will compile 50 times slower than if we put them all in one file. Ok, maybe only 25 times, but that is still a huge figure.

With really old editors it could be a problem to work on multiple places unless in separate files. But I can ask unlimited number of windows on the same file, and usually split any window to multiple panes too. And drop bookmarks with a single stroke that are preserved until deleted.

And for documentation/reading purposes we have doxygen -- it will show the classes separately, with inline code removed, etc. Regardless of how classes appear in sources.

Even if I chose to read the source directly, since when is a "class" a basic unit? It was always "package", "module, "library" or something alike that, having a concise philosophy, and a fair collection of classes, functions supporting enums, macros and other gizmos. Best if all co-located rather than spread all over the place.

A set of problems referred in the question is actually coming from this artificial split. Declarations must be maintained in headers? How many of those is actually for "export" and how many for internal use inside a component? Majority of the latter ones is not necessary without the split, as the function will just be a static one defined early on. What is so much lighter maintenance-wise.

The only drawback from join is that static/at-namespace object could clash. But those are hopefully blacklisted long time ago.

How about refactoring the system toward a state that causes less trouble? And not introducing headaches artificially following some ideas left behind by Noah?

Not the answer you're looking for? Browse other questions tagged or ask your own question.