Mission Impossible / How to create datatypes which cannot contain invalid state
adrianimboden
22.6K views
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// {
#include <boost/assert.hpp>
#include <iostream>
#include <mutex>
#include <thread>
void long_running_task() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// }
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();
{
The problem here is, that we have three bool variables which would result in possible state combinations, of which only are used:
is_connected | is_sending | is_receiving |
---|---|---|
false | ignored | ignored |
true | false | false |
true | false | true |
true | true | false |
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.
Create your playground on Tech.io
This playground was created on Tech.io, our hands-on, knowledge-sharing platform for developers.