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 there any alternate way of writing this?
exp_rsp_status = 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;
Ternary operators can be a more concise way in limited situations (your example is a good one). BUT they can often be abused to make code unreadable (which is a cardinal sin) = do not nest ternary operators!
The conditional ternary operator can definitely be overused, and some find it quite unreadable. However, I find that it can be very clean in most situations that a boolean expression is expected, provided that its intent is clear.
You could use a branchless ternary operator, sometimes called bitselect ( condition ? true : false). Don't worry about the extra operations, they are nothing compared to the if statement branching.
There should not be a major difference in speed. However, the switch is far more readable and maintainable. The switch option is easier to debug.
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.
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() && !isIgnorableProtoViol() && !isIgnorablePciAbort() ) { ret = req.slv_req[0].get_rsp_status(); } return ret; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With