3
\$\begingroup\$

I watched Robert Martin's programming 101 video. I'll explain what problem he talks about in the video for those of you who haven't watched it.

Robert Martin is in his house and shows a light switch. If this switch goes up, a light is on. If switch goes down, the light is off. He wrote a code to handle it. This is simple but the situation is getting more complicated when he shows second, third and fourth light switch. All of them change state of the same light. Each of them is dependent on the others. He also shows how to handle it in a code.

Robert Martin explains that this code is not the best solution because there can be a situation when two people come to the same room and they want to turn the light on. He demonstrated that the code he has written doesn't work well because the light doesn't change its state. It's happening because a computer doesn't see simultaneous events. One of them occurs first. That's happening when two people want to turn on the light. It happens simultaneously only from human perspective. For a computer one person changes state of the light first so in fact the state of the light is changed twice. Fortunately Uncle Bob shows how to solve this problem. He measures time elapsed from the last light state change. If the next change is happening in 500 ms, he rejects it. Now when two people want to turn the light on the light is actually on.

Unfortunately implementing above solution breaks a case when one person quickly turns on and turns off the light. Because the second change is happening in 500 ms it is rejected. So it is not possible to quickly turn on and turn off the light. Uncle Bob didn't show how to solve this problem. He left it as a homework.

It took me long to figure it out how can I solve this but finally I've came to a working solution. I'm not sure though if my solution is good or maybe I only think it is good. Can you tell me if I solved the problem correctly or there is a better solution?

Uncle Bob's code looks like this:

void lightLogic() {
    boolean a = switchA.isUp();
    boolean b = switchB.isUp();
    boolean c = switchC.isUp();
    boolean d = switchD.isUp();
    int currentSwitchState = 0;

    int base=10;
    if (a) currentSwitchState += base*base*base;
    if (b) currentSwitchState += base*base;
    if (c) currentSwitchState += base;
    if (d) currentSwitchState += 1;

    int currentTime = millis();
    if (currentSwitchState != lastSwitchState) {
        if (currentTime - lastChangeTime > 500) {
            light.state = !light.state;
            lastChangeTime = currentTime;
        }
    }

    lastSwitchState = currentSwitchState;

}

I introduced a new variable called penultimateSwitchState and I check if the current state of the light equals to penultimate state. If yes, I know that the same switch has been used to change light state so I accept that change:

void lightLogic() {
    boolean a = switchA.isUp();
    boolean b = switchB.isUp();
    boolean c = switchC.isUp();
    boolean d = switchD.isUp();
    int currentSwitchState = 0;

    int base=10;
    if (a) currentSwitchState += base*base*base;
    if (b) currentSwitchState += base*base;
    if (c) currentSwitchState += base;
    if (d) currentSwitchState += 1;

    int currentTime = millis();
    if (currentSwitchState != lastSwitchState) {
      if ((currentTime - lastChangeTime > 500) || (penultimateSwitchState == currentSwitchState)) {
        light.state = !light.state;
        lastChangeTime = currentTime;
      }
      penultimateSwitchState = lastSwitchState;
    }

    lastSwitchState = currentSwitchState;
  }
}
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

Welcome to the world of programming where you never know exactly when you're done because the client doesn't know precisely what they want!

The business solution is to this problem is to first sit down with the client, write down a proposal about some precise interpretation of what should happen. Have the client agree with this interpretation and sign it. Then implement this exactly as proposed.

Then later on when the client tests your code they're either happy (yay, we're done!) or they are not. If they're not you write down what they want to change and make them pay for it again. Happy times! (for you at least, the client doesn't like paying again for something "you should have known" in the first place).

Extra note: sometimes a client asks for something that cannot work. They might only realise the contradiction after you explained what exactly happens and offer them 2 possible ways that achieve 1 of the 2 things they ask for. In such a case you let the client decide which of the 2 is more important.


To write working code we first need to know which exact result we want to get.
Here's the current problem more precisely:

1) If 2 (or more) different switches are flipped within a certain time (half a second) of each other, ignore one of them and change the state of the light.
2) If a switch is flipped twice in rapid succession the second flip should not be ignored.

Question: If (1) and (2) happen within half a second, should the light change state?

My answer would be: If a switch is flipped once within half a second, the light should change state (compared to half a second earlier) irregardless of other switches.


Solution 1:

One possible solution is to modify the implementation of the switches themselves. If a switch is first in state off, and then flipped on, it will still say it's off for half a second. Only if it has remained on for half a second will it say it's turned on. This solves the problem of flipping the same switch twice in rappid succession ... with the downside that a light state will change at the earliest half a second after a switch is flipped. This is something the client has to agree on.

If the client agrees on that change, the lightLogic can remain exactly the same as in Bob's solution since that correctly handles the remaining requirement of ignoring multiple switches flipping "simultaniously".

This kind of solution is often used to prevent flickering in oversensitive keys. Consider a switch that has a bad contact. If switched halfway, it might rappidly say ON>OFF>ON>OFF even though you only flicked it once. Looking at a change from a small delay before makes sure that it will only fire once in such cases as intended.


Solution 2:

Alternatively if you require that the light switches on immediatly after a single switch is flipped, you'll have to do it differently.

Let's say the light is switched from off to on. For the next 500 milliseconds, under which condition should the light be switched back off? -> If ALL switches are back to what they were when the light was still off.

This looks similar to your idea, but only looking at the penultimate state isn't enough. If switch A is flipped, and then switch B is flipped 4 times, the light should still be ON because of switch A.

An idea would be like this (note not tested):

int currentTime = millis();
if (currentTime - lastChangeTime <= 500) { // might be considered simultanious
    if(currentSwitchState == initialSwitchState) { //all switches back to initial state
        light.state = initialLightState;
        lastChangeTime = 0; //respond to any new flip after this as if it was new.
    } // else light stays as it is now
} else {
    if (currentSwitchState != previousSwitchState) { // start new case
        light.state = !light.state;
        lastChangeTime = currentTime;
        initialSwitchState = previousSwitchState;
    } // else nothing changed
}
previousSwitchState = currentSwitchState; //always update latest known state
\$\endgroup\$
3
  • \$\begingroup\$ I like your second solution. To make the code work properly I had to add a new variable called lastLightState and then assign it to initialLightState when new case is going to happen. If I understand correctly, there is no one correct answer to this problem because all depends on requirements. Am I right? \$\endgroup\$ Commented Oct 1, 2019 at 11:17
  • \$\begingroup\$ Yes. It always depends on what you are trying to solve. Most often some details are missing so it's a good idea to write code in such a way that you can change it relatively easy later on, while at the same time only writing the code that is needed at that time to solve the part of the problem that is actually known to you. It's almost always impossible to tell which is the best solution (if one such solution even exists). \$\endgroup\$
    – Imus
    Commented Oct 1, 2019 at 11:22
  • \$\begingroup\$ Thank you for clarification and potential solutions. \$\endgroup\$ Commented Oct 1, 2019 at 17:26
1
\$\begingroup\$

Your implementation is interesting, but it does not save from waiting in some cases: If two users U1 and U2 switch on simultaneously and U2 switch was handled after U1, then U1 have to wait 500 ms for switch-off.

I have following suggestion: What if we just compare currentSwitchState and lastSwitchState and state can be result of this comparison:

if (currentSwitchState != lastSwitchState) {
    light.state = currentSwitchState > lastSwitchState;
}
enter code here

In this case we can remove waiting and some code.

I hope it makes sense. :)

\$\endgroup\$
1
  • \$\begingroup\$ Unfortunately, it can't look like this. State of a light should change regardless if the current switch state is greater or less than last switch state. Let's imagine that all four switches are down. Light is off. When first switch goes up (current switch state is greater than last switch state) light is going to be on but then when second switch goes up the light is going to be off even though the current switch state is greater than last switch state. In your implementation state of light will not change if subsequent swiches will go up. \$\endgroup\$ Commented Sep 26, 2019 at 6:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.