Click here to Skip to main content
15,879,348 members
Articles / Programming Languages / C++
Article

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

Rate me:
Please Sign up or sign in to vote.
4.79/5 (27 votes)
4 Nov 2014CPOL6 min read 27.8K   27   8
Let me use your functionality without using 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. <!--more--> 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:
import serial
ser = serial.Serial(0)
ser.write("hello")
ser.close()
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:
void  CSerialPort::open( )
{
	MRPT_START
	… 
	// Open the serial port:
	if ( INVALID_HANDLE_VALUE == (
	            hCOM = CreateFileA( m_serialName.c_str(), // Serial Port name
And in the header:
class HWDRIVERS_IMPEXP CSerialPort : public CStream
		{
			friend class PosixSignalDispatcherImpl;
		public:
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:
template <typename SerialPortService = serial_port_service>
class basic_serial_port
  : public basic_io_object<SerialPortService>,
    public serial_port_base
{
public:
You have to dig deeply into win_iocp_serial_port_service.ipp to actually find the code that opens the port in Win:
boost::system::error_code win_iocp_serial_port_service::open(
    win_iocp_serial_port_service::implementation_type& impl,
    const std::string& device, boost::system::error_code& ec)
{
  if (is_open(impl))
  {
    ec = boost::asio::error::already_open;
    return ec;
  }

  // Open a handle to the serial port.
  ::HANDLE handle = ::CreateFileA(name.c_str()
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):
class SerialPort{
public:
	void write(char c);
};

class MyBaseStream{
	virtual void write(std::string str)=0;
	friend MyBaseStream& operator <<(MyBaseStream& mystream, std::string str){
		mystream.write(str);
		return mystream;
	}
};

template <typename T>
class Stream: public MyBaseStream{
public:
	Stream(T& _port): port(_port){}
	virtual void write(std::string str){
		for(auto c: str)
			port.write(c);
	}
private:
	T port;
};

//example client code of generic streams
void write2stream(MyBaseStream& stream, std::string str){
	stream<<str;
}

using SerialPortStream = Stream<SerialPort>;

int main(){
SerialPort serial;
    SerialPortStream serial_stream(serial);
    serial_stream<<"Hello World\n";
    write2stream(serial_stream, "Good Bye\n");
}
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer biicode
Spain Spain
biicode is a C/C++ dependencies manager with a hosting service. For C/C++ developers that think a dependency manager is needed, biicode is a multiplatform tool and hosting service that allows you to build your projects easily, integrate third party code and reuse code among projects with just #includes.
This is a Organisation

3 members

Comments and Discussions

 
GeneralMy vote of 5 Pin
tugrulGtx8-Oct-17 5:02
tugrulGtx8-Oct-17 5:02 
QuestionWhere do you parametrize the com port? Pin
TSchind5-Jan-15 23:24
TSchind5-Jan-15 23:24 
AnswerRe: Where do you parametrize the com port? Pin
DiegoRL7-Jan-15 23:27
DiegoRL7-Jan-15 23:27 
QuestionAs principle we need more than just a statement Pin
devCoder6-Nov-14 20:14
devCoder6-Nov-14 20:14 
GeneralC++ does not provide enough standard (and well-received) libraries. Pin
zeodtr6-Nov-14 14:06
professionalzeodtr6-Nov-14 14:06 
GeneralRe: C++ does not provide enough standard (and well-received) libraries. Pin
DiegoRL6-Nov-14 22:25
DiegoRL6-Nov-14 22:25 
Question100% agree Pin
studleylee5-Nov-14 9:51
studleylee5-Nov-14 9:51 
QuestionSo true Pin
Frans_551295-Nov-14 7:31
Frans_551295-Nov-14 7:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.