A software design principle: Don’t make me use your design

Or why there is no SerialPort in C++

Developing code in C++ for robotics, I often faced the problem of communicating via serial port with a robot, a sensor or any other device. C and C++ are languages supposedly very close to hardware. Furthermore, they are the most common and oldest mainstream programming languages out there. So communicating over a serial port in a portable way should be straightforward.

Probably as straightforward as it is in python. To send a few bytes, with just a bare python installation, all you have to do (in any OS) is:

$ pip install pyserial

this downloads and sets up a dozen files, just a few KBs. Then, write your code:

And that’s it!

Lets do it now in C++. Suppose you want to write a similar application just to send some bytes over the serial port. The first difficulty is that seems there’s nothing as “standard” or widely accepted, so my first attempt was to use MRPT. MRPT is a robotics library plenty of great drivers, algorithms, tools, interfaces… an amazing and very popular tool kit for robotics. I could use the whole MRPT, but lets say I am not willing to introduce a dependency to it, it is very large (60Mb of source code, >5k source files) and contains tons of stuff I do not need. I want to get just the SerialPort functionality. Going down to the code, you can find a CSerialPort_win.cpp and a CSerialPort_lin.cpp files, both implementing the same CSerialPort class, declared in CSerialPort.h header file. In the implementation file you can find:

And in the header:

That’s it, the implementation relies on a macro MRPT_START (it allows profiling and exception handling), which seems could be easily removed, but the header inherits from CStream, a base class for streams (network, files, output, etc), which in turn depends on a serialization framework (CSerializable, CObject).

While the overall software design of the library is great, it is very difficult to isolate what, in my opinion, should be a very independent and low coupled component. Please, do not misunderstand my opinion, MRPT is really great. The problem is simply that the library was not designed with that goal in mind.

My guess was that something more generic, not tied to robotics, solution already existed. So I googled for it a little and it seemed that the most accepted solution (at least in stackoverflow) was using boost::asio. It seemed reasonable to me that depending on 500 Mb of source code is probably overkill for sending some bytes, so I also tried to extract the serial port functionality. But I found some high levels of abstractions over it, for example, in the file basic_serial_port.hpp:

You have to dig deeply into win_iocp_serial_port_service.ipp to actually find the code that opens the port in Win:

Surprisingly, the functionality is spread between different files, for example, the latter contains code for opening, closing and setting parameters, but the code for sending and receiving bytes is located in another file.

Basically the conclusion is that you cannot use the serial port separately, you are forced to use it with Asio. We’re facing the same problem again: boost is really amazing and asio is also brilliant. And surely their serial port implementations (both MRPT and boost::asio) are more configurable and powerful than the python one is. I personally wish someday I could write code half as good as the one in any of those projects. But I see a pattern here, one, that doesn’t allow to have a small and simple SerialPort functionality to just synchronously send and receive some bytes over the serial port. So I dare to state what IMHO could be considered as a design principle:

Let me use your functionality without using your design

Of course, this principle is nothing new. It is very related to many well known principles and patterns. It is very related to the trending functional programming approach, and could be considered as a consequence of many patterns as single responsibility, low coupling, separation of concerns, high cohesion, etc. But I have never seen it stated as above, and I think it could be another perspective to be taken into account. By “without using your design” I mean architectural design, obviously every single line of code has some design in it.

How do I think the SerialPort example should be addressed? The main problem I see with it, is the failure to identify SerialPort as a first level building block, and thus it deserves its own “package”, namespace, library… you named it. With that in mind, it could be very easy to build it in those libraries. For example, in the MRPT case, you can have both the SerialPort and a streams and serialization framework independent from each other. Then, bind both things very easily with templates (very simplified for clarity, just an idea):

In this way, both the SerialPort and the Stream classes become two independent first class citizens in our project. It becomes very easy to test (and mock), understand, maintain and extend them, and it is also simpler to grow and scale the whole project using them. I know I am not saying something very new with this software design proposal, for example it is similar to Alexandrescu’s policy based design, though the final goal could be different.

Failures to follow this principle are more common in C/C++

I have seen this pattern a few times in C and C++ projects, but very rarely in other languages (at least those that I have used more as java and python) and I believe there is a reason, not directly related to software design for it: the lack of a widely used dependency manager. And no, OS package managers, installers and so, are not enough to solve this problem. Even if the authors of these libraries decide to decouple the functionality of the SerialPort basic wrapper in their design, there is little gain in it, users should still manually extract those files and integrate them in their projects, which doesn’t sound as reasonable engineering and will produce, for sure, maintenance problems and lack of updates. It is really unlikely that the authors will decide to create a separate project/library for the SerialPort, it is more effort not only to do it in the short term, but also to maintain and work with it in the mid and long terms. So developers just roll out their designs, and fill the functionality in it as required. I’ve done it so many times too.

On the contrary, if a dependency manager existed, it is very likely that a simple, independent and robust SerialPort implementation would emerge and be widely adopted, and people creating asynchronous communication frameworks or robotics applications would use it and have less code to write.

Hopefully such a dependency manager for C and C++ will someday be a reality :D

 

If you liked this post please comment below. If you want to try biicode just click on the sidebar button and if you have any doubts check our docs and forum.


Related Posts
  • Pingback: Visto nel Web – 155 | Ok, panico()

  • Rob Grainger

    It’s all really just an application of the Single Responsibility Principle. The serial port class should be responsible for access to a serial port, not making the serial port appear as a stream.

    • http://about.me/diego.rlosada Diego

      Yes, very, very related, but not only. Also related to coupling. A very simplified example:.

      float distance(Point2D p1, Point2D p2){return sqrt((pow(p2.x-p1.x,2)+pow(p2.y-p1.y, 2);}

      It clearly has a Single Responsibility, but it is still coupled with a design (Point2D, which typically could be an instatiation of Point… and so on…).

      float distance(float x1, float y1, float x2, float y2);

      is not.

      Obviously not the best solution for this simple example, but if the distance algorithm had 100 lines long, IMHO it is better to code it as independent as possible of your current project types and classes, then build an adaptor/wrapper to it for your project.

  • Craig Brandenburg

    Yet another way of saying it: Make tools, not frameworks.

  • Steve Wolf

    Not possible for any code that is more than a little complex – and if it were only a little complex, you wouldn’t need to go get a library to help you manage it, or to have it written for you.

    The reality is that to write SerialPort, you have to sit on top of something – the OS specific APIs, or a language abstraction. C++ doesn’t have a good language abstraction for I/O – not disk i/o, not serial i/o. Don’t get me wrong: it has abstractions / language support – but its support is crufty at best, and needs some additional layers to make it sane (such as asio).

    Your own example of ‘distance’ function – that uses Point2D. That is a good interface. It *should* rely on Point2D – breaking everything down to char* and float (or double?) is a terrible idea. It drops what can be expressed down to a really lame crufty level of language-only support. And in C/C++, that level is terrible. Until you add things like asio and Point2D and so on, C++ is manual labor and does not lend itself to reducing work or solving problems.

    Does this suck? Yes. But that’s at the core of C++. Unless it is going to get really good core libraries, and finally solve platform-independent I/O, graphics I/O, windowing, GUI events, etc., it will always be mired in this exact issue: to make anything genuinely useful and expressive – you’re going to have drag a whole bunch of enabling libraries with your own library. I’m sorry. :(

    • http://about.me/diego.rlosada Diego

      Sure, the example about Point2D is an extreme pedagogical simplification, normally, you should rely on it in normal SW engineering.

      But the idea is still valid, you certainly do not need Asio (and its boost dependencies) to be able to send a few bytes over the serial port. Python pyserial is a great example, just a few files (at the end, they are also C!!!) that make a perfect, simple and modular level of abstraction over a HW serial port, not coupled with any asynchronous framework. There is nothing in C/C++ that disallows such modularity.

      Of course it is no sense to break down everything to primitive parameters, but there are many situations where it is extremely simple to introduce such an intermediate layer of abstraction, and benefit from the lower coupling. The presented Point2D – distance is a prototypical example: DataModel – Algorithm, when both tend to become complex. It might become more obvious with the STL, it is exactly the same principle we are applying:

      The way to decouple both is to introduce such a “primitivization” of the parameters. If you have a std::vector (our Point2D), and you want to std::sort (our distance) it, then:

      std::sort (v.begin(), v.end());

      std::sort implements a functionality very decoupled of the actual vector design. So decoupled that allows to sort also other containers. Nothing impedes to write a simple wrapper of the form mysort(v) to do exactly the same. My thesis is that this can and should be done more often that we are currently doing, we are many times doing mysort(v) distance(p1, p2) as the only design, but many many times, especially if Point2D couples with a large hiearchy of templates, serialization, maybe even visualization (I have seen it many times), then it is very necessary (assuming that the distance function is large and complex) to decouple at some point, as defining distance(p1.x, p1.y, p2.x, p2.y), and then a wrapper distance(p1, p2) that just uses the other. Otherwise, you will soon find that you need to compute distances onboard without display, in a very similar application, and you will not be able to use what you have done in your distance function, because it is coupled with your Point2D design (as visualization!) unnecesarily.

      • Steve Wolf

        I appreciate your goal. It is laudable, and I strive for it, as I think many others do.
        That said… I feel deeply defeated by C++. Hell, I can’t even deal with a string of text in a manner that lends itself to decoupled algorithms well. The truth (in my experience) is that the lowly string comes in so many different flavors, and all of them have idiosyncrasies, leading to software that ends up having multiple string libraries linked in.
        In my world, we have std::string, char*, TCHAR*, wchar_t*, CStringA, CStringW, BSTR, and a few lesser representations, and that doesn’t even account for the fact that some are represented as UTF-8 or UTF-16LE or local code page ANSI and on & on.
        Again – avoiding _gratuitous_ coupling is laudable. Trying to get truly decoupled algorithms is often impossible – compromises are the black-flies of my daily programming life. They crop up everywhere and multiply incessantly. Because the common ground is still too weak, and C++ doesn’t handle genuinely decoupled algorithms (though it’s not black and white, and I agree you can do what you can to strive towards that goal).

        But even in the case of a serial port interface – you don’t have to take asio, but you then limit yourself to something less general. You’re now tightly coupled to one OS’s serial I/O, such as Windows API. And you don’t dove-tail as nicely with other interfaces that are designed around asio. (I’m not particular to asio, just saying that things tend to work within domains to inter-operate, and it is because of C++ lack of good standard domain that we have this mess – python has a far more robust core library / language support for abstraction across platforms and good basic types, so doesn’t run into 25x string implementations or huge platform-independence libraries coming along for the serial-io ride).

        Call me jaded. I wish this were a lot easier in practice to accomplish. Personally, I hate the std libraries approach to decoupling, because it fails. distance() is a fine example: you’re limited to using “decoupled” algorithms against a tight subset of actual implementation types or else your performance goes into the crapper. std::vector will give you excellent performance there, but std::map won’t. I would have preferred a more tightly coupled sort(container) where its algorithm was tailored to the actual container’s implementation so that it could give you best-case performance in all cases, rather than pushing onto the programmer hidden requirements about performance characteristics that you cannot see in the code-as-written.

        Sorry, maybe this is getting dense with assumptions about how much I know or you know about how things work – but for my 2 cents – I think the STL approach was a flawed exercise by an academic mind-set, and ultimately has saddled C++ with a terrible standard.

        • http://about.me/diego.rlosada Diego

          I agree with the strings issues, not that much about the STL, but I would say that overall we mostly agree: It is necessary to have and to talk about these principles, even if it can be hard to apply them in practice many times. C++ can have some complexities, but I believe that the C++ community can definitely improve, things are getting much better with the latest standards. And I think things are not always black or white, so thinking about the problems and trying to improve the existing approaches is what at the end, slowly gets us out those hells; so thanks very much for your interesting contribution to this discussion.

  • Serge Pavlovsky

    you can write toy serialport class for use in toy program. asio is for real programs, and they already use asio anyway.