Mission Impossible / How to create datatypes which cannot contain invalid state

adrianimboden
6,468 views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

This example implements a serial port protocol which is request/response based. It assumes a that:

  • the send and receive is long running
  • the current status can be obtained for debugging reasons
  • calls to send_request are synchronized by the caller
Example
// {...}
class SerialPort {
public:
void connect();
std::string send_request(const std::string& request);
void print_state(std::ostream& os) const;
private:
mutable std::mutex guard_;
bool is_connected_ = false;
bool is_sending_ = false;
bool is_receiving_ = false;
};
void SerialPort::connect() {
long_running_task();
std::scoped_lock lock{guard_};
BOOST_ASSERT_MSG(not is_connected_, "port already connected yet");
is_connected_ = true;
}
std::string SerialPort::send_request(const std::string& request) {
{
std::scoped_lock lock{guard_};
BOOST_ASSERT_MSG(is_connected_, "port not connected yet");
BOOST_ASSERT_MSG(not is_sending_, "detected race");
BOOST_ASSERT_MSG(not is_receiving_, "detected race");
is_sending_ = true;
}
long_running_task();
{
std::scoped_lock lock{guard_};
BOOST_ASSERT_MSG(is_connected_, "detected race");
BOOST_ASSERT_MSG(is_sending_, "detected race");
BOOST_ASSERT_MSG(not is_receiving_, "detected race");
is_sending_ = false;
is_receiving_ = true;
}
long_running_task();
{
std::scoped_lock lock{guard_};
BOOST_ASSERT_MSG(is_connected_, "detected race");
BOOST_ASSERT_MSG(not is_sending_, "detected race");
BOOST_ASSERT_MSG(is_receiving_, "detected race");
is_receiving_ = false;
}
return "ack";
}
void SerialPort::print_state(std::ostream& os) const {
std::scoped_lock lock{guard_};
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The problem here is, that we have three bool variables which would result in 2×2×2=8 possible state combinations, of which only 4 are used:

is_connectedis_sendingis_receiving
falseignoredignored
truefalsefalse
truefalsetrue
truetruefalse

This leads to the following results:

  • A person which reads the code has to look up all the various asserts in order to understand the existing invariants
  • A person which debuggs the code has to keep values of supposedly relevant variables in his head, which are actually not used
  • It is very easy for a different programmer to change the code in such a way that the previous invariant is no longer given

Now try to refactor the SerialPort example to use an enum instead.

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content