Changes between Version 6 and Version 7 of BestPracticeHandbook


Ignore:
Timestamp:
May 5, 2015, 10:00:37 AM (7 years ago)
Author:
Niall Douglas
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • BestPracticeHandbook

    v6 v7  
    1919There was very significant divergence in best practices between these libraries at the time of examination, and moreover the divergences were uneven possibly reflecting an uneven understanding of what best practice might be in C++ 11/14. Thence from my examination I have prepared the list below of what I felt were best practices in C++ 11/14 mandatory libraries with hyperlinks to the relevant source code files in the relevant libraries above. As what a "best practice" is or is not may vary according to opinion, I have attempted to provide a rationale for each of the suggestions below. It is up to library authors to weigh each suggestion and to decide whether to apply it, or not, to their library.
    2020
    21 One of the most strikingly consistent features of these new libraries is the lack of dependence on Boost, even to the extent of not using the boost namespace as well as no Boost at all. The big advantage of this is modularity, so almost all of these libraries can be used standalone which is an oft requested feature by Boost end users. However this is likely not sustainable as more C++ 11/14 libraries become useful to other C++ 11/14 libraries and coupling therefore increases. I therefore dedicate significant effort below into how best to couple your library to other libraries which has very significantly improved with new C++ 11 features.
     21One of the most strikingly consistent features of these new libraries is the lack of dependence on Boost, even to the extent of not using the boost namespace as well as no Boost at all. The big advantage of this is modularity, so almost all of these libraries can be used standalone which is an oft requested feature by Boost end users. However this is likely not sustainable as more C++ 11/14 libraries become useful to other C++ 11/14 libraries and coupling therefore increases. I therefore dedicate significant effort below into how to most flexibly couple your library to other libraries to leave options open, the techniques for which have very significantly diversified in C++ 11.
    2222
    2323One will note that the list below is much more C++ 11/14 focused than Boost focused. This is because it is derived from the first crop of C++ 11/14 mandatory Boost libraries. This is not a handbook for writing Boost libraries or even C++ 11/14 Boost libraries, if you want that [http://www.boost.org/development/requirements.html first start reading here] (note some of the guidelines on that page don't really apply to C++ 11/14 libraries) and [http://rrsd.com/blincubator.com/requirements/ then read here] and [http://rrsd.com/blincubator.com/advice/ here].
     
    2929== 1. Strongly consider using git and [https://github.com/ GitHub] to host a copy of your library and its documentation ==
    3030
    31 All the Boost libraries are on github, and all the free tooling exampled below integrates easily with github. Choosing github makes your life much easier. Note that as git is a distributed source code control system, you can keep a canonical master copy anywhere and write a script which autorefreshes the github copy, thus triggering any of the free tooling you have integrated there.
     31There are many source code control systems, with subversion and CVS being the two most recently popular of yesteryear. Probably the current most popular source code control system is Git, and despite its (Mingw i.e. non-native) port on Microsoft Windows being a bit flaky, it is very useful once mastered.
     32
     33There is less widespread consensus about where to host your git repositories, with the most popular by far being github which is a proprietary service run by a profit making company. Nevertheless, one often hears strong arguments in favour of gitlab, bitbucket and many other alternatives.
     34
     35All the Boost libraries are on github, as are all the libraries I reviewed. The huge advantage of github over all others is that the free tooling exampled below integrates easily with github. Choosing github therefore makes your life much easier. Note that as git is a distributed source code control system, you can keep a canonical master copy anywhere and write a script which autorefreshes the github copy, thus triggering any of the free tooling you have integrated there. In other words, don't necessarily place all your eggs in the github basket, and consider making github simply a medium for conveniently triggering the free tooling.
    3236
    3337Github also provides free website hosting for HTML. Have a script automatically generate documentation and commit it to the gh-pages branch in your normal repo. This should present a copy of your HTML at !http:// ''username''.github.io/''repository''.
     
    1921962. This technique is highly extensible to allow ''dependency injection'' of STL11 vs Boost on a per-feature basis e.g. your user wants Boost.Thread instead of STL11 thread but only for threading, so your library can be so modular as to allow both options to end users. This is covered in a separate recommendation below.
    193197
    194 
    195 == 3. Strong consider trying your library on Microsoft Visual Studio 2015 ==
     198Examples of libraries which use versioned namespaces and aliasing to bind a namespace locally:
     199
     200* https://boostgsoc13.github.io/boost.afio/
     201
     202
     203== 3. Strongly consider trying your library on Microsoft Visual Studio 2015 ==
    196204
    197205More than half the libraries reviewed had no support for Microsoft Visual Studio, and only supported GCC and clang. When the authors were asked why, in many cases it was simply assumed that MSVC didn't implement much C++ 11/14 and the authors hadn't attempted MSVC support.
     
    211219I am not claiming that you won't get surprises when you try getting MSVC to compile your code which you thought was standards compliant. MSVC is not an AST based compiler and uses heuristics to trigger partial AST compilation, and therefore has a unique processing model which exposes your assumptions about what you think or thought is valid C++. This is exactly why it is worthwhile getting your C++ 11/14 library working on MSVC because you will get a better quality, more standards conforming C++ library out of it.
    212220
     221A good example of just how much C++ 14 support VS2015 provides is in Boost.DI. When I first contacted the author about the lack of VS2015 support, he proceeded to port his entirely C++ 14 codebase to VS2015 successfully, though he had to do a bit of refactoring to make the port work. Interestingly because he didn't push constexpr too far in DI, VS2015's constexpr support was enough for DI to work as expected on VS2015 for the most part.
     222
     223* https://krzysztof-jusiak.github.io/di/boost/libs/di/doc/html/
     224
     225
    213226== 4. Consider making it possible to use an XML outputting unit testing framework, even if not enabled by default ==
    214227
    215228A very noticeable trend in the libraries above is that around half use good old C assert() and static_assert() instead of a unit testing framework.
    216229
    217 There are many very good reasons not to use a unit testing framework by default, but there are few good reasons to not be able to use a unit testing framework at all. A big problem for the release managers when your library cannot output XML indicating exactly which tests pass and which fail (including the static ones) is that all they get instead is failure to compile or failure to execute. This forces them to dig into compiler error diagnostics and unit test diagnostics respectively. It also makes what may be a very minor problem easily delegated appear as serious as the most serious possible problem because there is no way to quickly disambiguate without diving into potentially a debugger, so all these are good reasons to support some XML outputting unit testing framework which reports an XML entry one per test for each test case in every test suite in your library.
     230There are many very good reasons not to use a unit testing framework by default, but there are few good reasons to not be able to use a unit testing framework at all. A big problem for the Boost release managers when your library cannot output XML indicating exactly which tests pass and which fail (including the static ones) is that all they get instead is failure to compile or failure to execute. This forces them to dig into compiler error diagnostics and unit test diagnostics respectively. It also makes what may be a very minor problem easily delegated appear as serious as the most serious possible problem because there is no way to quickly disambiguate without diving into potentially a debugger, so all these are good reasons to support some XML outputting unit testing framework which reports an XML entry one per test for each test case in every test suite in your library.
    218231
    219232Let me give you an example with Boost.AFIO which executes about a hundred thousand tests for about 70 test platforms and configurations per commit. I once committed a change and noticed in the test matrix that only statically linked libraries were failing. The cause was immediately obvious to me: I had leaked ABI in a way that the unit tests which deliberately build mismatching versions of AFIO to ensure namespace version changes don't conflict had tripped, and without even having to investigate the error itself I knew to revisit my commit for ABI leakage. For someone less familiar with the library, a quick look into the failing test would have revealed the name of the failing test case and instantly realise it was an ABI leakage problem. This sort of extra information is a big win for anyone trying to get a release out the door.
     
    223236Finally, specifically for Boost libraries we have an [http://www.boost.org/development/tests/master/developer/summary.html automated regression testing] system which works by various end users uploading XML results generated by Boost.Test to an FTP site where a cron script regularly runs to generate static HTML tables of passing and failing tests. Needless to say, if your library was as useful as possible to that system everybody wins, and your library is not as useful to that system if it uses assert() and even static_assert() because the XML uploaded is a compiler error console log or an assert failure diagnostic instead of a detailed list of which tests passed and which failed.
    224237
    225 Hopefully by now I have persuaded you to use an XML outputting unit test framework. If you are a Boost library, the obvious choice is to use Boost.Test. Despite its many problems, being an arse to develop against and lack of maintenance, Boost.Test is still a very competitive choice, and if you ignore the overly dense documentation and simply lift the pattern from this quick sample you'll be up and running very quickly:
     238Hopefully by now I have persuaded you to use an XML outputting unit test framework. If you are a Boost library, the obvious choice is to use Boost.Test. Despite its many problems, being slow to develop against and lack of maintenance in its release branch, Boost.Test is still a very competitive choice, and if you ignore the overly dense documentation and simply lift the pattern from this quick sample you'll be up and running very quickly:
    226239
    227240{{{#!C++
     
    262275 BOOST_FAIL(msg):: Immediately exit this test case with a message.
    263276
    264 Boost.Test provides an enormous amount of extra stuff (especially in its unstable branch), but for 99% of C++ code the above is all you will ever need. There is also a very specific reason I chose this exact subset of Boost.Test's functionality to suggest using here, because [https://github.com/ned14/Boost.APIBind/blob/master/include/boost/test/unit_test.hpp Boost.APIBind]'s lightweight header only Boost.Test emulation defines just the above subset and usefully does so into a header inside APIBind called "boost/test/unit_test.hpp", so if you include just that header you get compatibility with APIBind and Boost.Test. In other words, by using the pattern just suggested you can:
     277Boost.Test provides an enormous amount of extra stuff (especially in its unstable branch), but for 99% of C++ code the above is all you will ever need as more complex checks can be synthesised from the primitives above. There is also a very specific reason I chose this exact subset of Boost.Test's functionality to suggest using here, because [https://github.com/ned14/Boost.APIBind/blob/master/include/boost/test/unit_test.hpp Boost.APIBind]'s lightweight header only Boost.Test emulation defines just the above subset and usefully does so into a header inside APIBind called "boost/test/unit_test.hpp", so if you include just that header you get compatibility with APIBind and Boost.Test. In other words, by using the pattern just suggested you can:
    265278
    2662791. With a macro switch turn on full fat Boost.Test.
     
    2682813. For those so motivated that they really want assert() and nothing more, simply wrap the above macros with calls to assert(). Your single unit test code base can now target up to three separate ways of reporting unit test fails.
    269282
    270 Note if CATCH doesn't have enough features and Boost.Test is too flaky, another popular choice is [https://code.google.com/p/googletest/ Google Test].
     283Note if CATCH doesn't have enough features and Boost.Test is too flaky, another popular choice with tons of bells and whistles is [https://code.google.com/p/googletest/ Google Test].
    271284
    272285What are the problems with replacing asserts with a unit test framework?
     
    2852983. Asserts have no effect when NDEBUG is defined. Your test code may assume this for optimised builds, and a simple regex find and replace may not be sufficient.
    286299
     300Libraries implementing XML outputting unit testing with the Boost.Test macro API:
     301
     302* https://github.com/boostorg/test
     303* https://github.com/ned14/Boost.APIBind
     304
    287305== 5. Consider not doing compiler feature detection yourself ==
    288306