I came across this code written by someone else. Is this usage of the conditional operator recommended or commonly used? I feel it is less maintainable - or is it just me? Is th
Common or recommended? No.
I did something similar, but I had my reasons:
It was DEBUG CODE that was never going into a release.
textprintf_ex(gw->GetBackBuffer(), font, 0, 16, WHITE, -1, "BUTTON: %s",
//If... Then Display...
(ButtonClicked(Buttons[STOP]) ? "STOP"
: (ButtonClicked(Buttons[AUTO]) ? "AUTO"
: (ButtonClicked(Buttons[TICK]) ? "TICK"
: (ButtonClicked(Buttons[BLOCK]) ? "BLOCK"
: (ButtonClicked(Buttons[BOAT]) ? "BOAT"
: (ButtonClicked(Buttons[BLINKER]) ? "BLINKER"
: (ButtonClicked(Buttons[GLIDER]) ? "GLIDER"
: (ButtonClicked(Buttons[SHIP]) ? "SHIP"
: (ButtonClicked(Buttons[GUN]) ? "GUN"
: (ButtonClicked(Buttons[PULSAR]) ? "PULSAR"
: (ButtonClicked(Buttons[RESET]) ? "RESET"
: /*Nothing was clicked*/ "NONE"
)))))))))))
);
The only reason I did not use an if-else chain was it would have made the code immense and harder to follow because all I needed to do was print a word to the screen.
This is terrible code.
While it is often desirable to initialize a variable with a single expression (for example, so we can make it const
), this is no excuse to write code like this. You can move the complex logic into a function and call it to initialize the variable.
void
example(const int a, const int b)
{
const auto mything = make_my_thing(a, b);
}
In C++11 and later, you can also use a lambda to initialize a variable.
void
example(const int a, const int b)
{
const auto mything = [a, b](){
if (a == b)
return MyThing {"equal"};
else if (a < b)
return MyThing {"less"};
else if (a > b)
return MyThing {"greater"};
else
throw MyException {"How is this even possible?"};
}();
}
That's just horrible code.
So what can you do?
if
).return
s. Nobody likes deep indentation.And just to be clear: There's nothing wrong with the ternary operator. If used judiously, it often produces code that's easier to digest. Avoid nesting them though. I occasionally use a second level if the code is crystal clear, and even then I use parentheses so my poor brain doesn't have to do extra cycles decyphering the operator precedence.
Care about the readers of your code.
Others already said how awful that code excerpt is, with nice explanations. I will just provide few more reasons why that code is bad :
if you consider one "if-else" to implement exactly one feature, then it is clear how complex that code is. In your case, I can not even count number of ifs.
It is obvious that your code is breaking breaking the single responsibility principle, which tells :
...a class or module should have one, and only one, reason to change.
unit testing that would be a nightmare, which is another red flag. And I bet that your colleague didn't even try to write unit tests for that piece of code.
Perhaps this is in a device driver's message loop and the original coder, possibly 10 years ago, didn't want jumps in the code. I hope he verified that his compiler didn't implement the ternary operator with jumps!
Examining the code, my first remark is that a sequence of ternary operators is -- like all code -- better readable when adequately formatted.
That said, I'm not sure that I parsed the OP's example correctly, which speaks against it. Even a traditional nested if-else construct would be hard to verify. This expression violates the fundamental programming paradigm: Divide and Conquer.
req.security_violation
? dis_prot_viol_rsp && is_mstr
? uvc_pkg::MRSP_OKAY
: uvc_pkg::MRSP_PROTVIOL
: req.slv_req.size()
? is_mst_abort_rsp && dis_mst_abort_rsp
|| req.slv_req[0].get_rsp_status()==uvc_pkg::MRSP_PROTVIOL
&& dis_prot_viol_rsp
|| is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp
? uvc_pkg::MRSP_OKAY
: req.slv_req[0].get_rsp_status()
: uvc_pkg::MRSP_OKAY;
I wanted to check how the code looks when refactored. It sure is not shorter but I like how the speaking function names make the intent clearer (of course I guessed here). This is, to some degree, pseudo code because the variable names are probably not global so that the functions would have to have parameters, making the code less clear again. But perhaps the parameter could be a single pointer to a status or request structure or such (from which values like dis_prot_viol_rsp
have been extracted). Whether or not to use a ternary when combining the different conditions is up to debate. I find it often elegant.
bool ismStrProtoViol()
{
return dis_prot_viol_rsp && is_mstr;
}
bool isIgnorableAbort()
{
return is_mst_abort_rsp && dis_mst_abort_rsp;
}
bool isIgnorablePciAbort()
{
return is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp;
}
bool isIgnorableProtoViol()
{
return req.slv_req[0].get_rsp_status()==uvc_pkg::MRSP_PROTVIOL && dis_prot_viol_rsp;
}
eStatus getRspStatus()
{
eStatus ret;
if( req.security_violation )
{
ret = ismStrProtoViol() ? uvc_pkg::MRSP_OKAY : uvc_pkg::MRSP_PROTVIOL;
}
else if( req.slv_req.size() )
{
ret = isIgnorableAbort()
|| isIgnorableProtoViol()
|| isIgnorablePciAbort()
? uvc_pkg::MRSP_OKAY
: req.slv_req[0].get_rsp_status();
else
{
ret = uvc_pkg::MRSP_OKAY;
}
return ret;
}
Finally we can exploit the fact that uvc_pkg::MRSP_OKAY
is kindof the default and only overwritten under certain circumstances. This eliminates a branch. Look how after some chiseling the code's reasoning is nicely visible: If it's not a security violation look closer and check the actual request status, minus empty requests and ignorable aborts.
eStatus getRspStatus()
{
eStatus ret = uvc_pkg::MRSP_OKAY;
if( req.security_violation )
{
ret = ismStrProtoViol() ? uvc_pkg::MRSP_OKAY : uvc_pkg::MRSP_PROTVIOL;
}
else if( req.slv_req.size()
&& !isIgnorableAbort()
&& !isIgnorablePorotoViol()
&& !isIgnorablePciAbort()
)
{
ret = req.slv_req[0].get_rsp_status();
}
return ret;
}
What an ugly mess. I broke it out into if and else's just to see what it was doing. Not much more readable, but thought I'd post it anyways. Hopefully someone else has a more elegant solution for you. But to answer your question, don't use ternaries that complicated. No one wants to do what I just did to figure out what it's doing.
if ( req.security_violation )
{
if ( dis_prot_viol_rsp && is_mstr )
{
exp_rsp_status = uvc_pkg::MRSP_OKAY;
}
else
{
exp_rsp_status = uvc_pkg::MRSP_PROTVIOL;
}
}
else if ( req.slv_req.size() )
{
if ( ( is_mst_abort_rsp && dis_mst_abort_rsp ||
( req.slv_req[0].get_rsp_status() == uvc_pkg::MRSP_PROTVIOL && dis_prot_viol_rsp ) ||
( is_mst_abort_rsp && req.is_pci_config_req() && dis_pcicfg_mst_abort_rsp ) )
{
exp_rsp_status = uvc_pkg::MRSP_OKAY;
}
else
{
exp_rsp_status = req.slv_req[0].get_rsp_status();
}
}
else
{
exp_rsp_status = uvc_pkg::MRSP_OKAY
}