|more useful options|
I think the precedence thing has been covered, but since DeMorgan's and "unless" got mentioned, a few thoughts about:
1. reducing Not's - this is a good idea!
I agree that factoring out negations improves readability.
If you get too many "not's" in there it can get confusing.
Read as: I'm not done if I've got some foo or bar. To me this is easier than I'm done if I don't have this and I don't have that, but situations vary.
As far as converting between "AND's" and "OR's", I always draw a picture, not a table or an equation - just some little doodle on a piece of scratch paper.
When looking at (!$self->foo && !$self->bar), I would draw an AND gate with two bubbles on the inputs, interpreted as both low's on input means a high output, then by inspection see that this means that any high input yields a low output. Well, that is a NOR gate. So I would draw an OR gate with a bubble on the output. Then I would compare pictures and verify that they really do the same thing.
So instead of working with some De Morgan's equation, work with the picture. Ask a hardware guy to show you how they do it. 30 minutes learning how to draw and interpret these pictures (simple logic circuit diagrams) will save you a lot of grief.
If things get too hairy to draw a simple picture, then simplify things. Maybe you combine some things together in an intermediate expression which is used in the following statement, or something like that...
I guess I'm getting far afield, but there is another technique when things get REALLY hairy, the table driven approach. This is something to consider with "monster if's"... Depending upon the situation, it might or might not work out to be easier to understand and maintain.
In something like C, you could make maybe an array with 8 dimensions. So you take say the 8 input conditions and do a lookup into this logic table to decide what to do. Another way to think of this is like a memory lookup, all possible combinations of True/False with 8 inputs requires 8 bits, or 256 "addresses".
In Perl, the way to do this is with a hash table! With say 8 inputs, its likely that the hash won't have all 256 keys because probably many combinations "can't happen", ha! ha!
In C, you would have to make "1's" and "0's" out of the input conditions to use as the array indicies. In Perl we don't have to do that! A hash key can even look like this: "0;Level3;;Ok;BAD;1...". I don't have Larry's book with me right now, but if I remember right, something like this:
will put in a semicolon between conditions in the key. That way even a null string will result in something going into the key rather than "nothing". I think if things are really this hairy, some extra code to convert the 0,1, etc into something more comprehensible is worth it (helps in the table interpretation).
Of course with the hash approach, you aren't limited to just true/false for a value, you can have multi-value inputs like: level 1,2,3,4 or whatever.
Now as the to values of this hash....sky is the limit....Maybe you have a name that describes the situation, "ReadyforTakeOff", or whatever. You can test these "conclusion" names with some easy if statements instead of using hairy 8-12 term "if" to make 'em in the first place. You can put function address values and use hash like a dispatch table, etc.
It possible to build some really hairy logic into the table. If you've got the right application for this technique, changing the decision table will be easier than adding more "if code". If you feed some of the results of the actions into the decision maker, then you have a state machine implemented with a hash!
This technique doesn't come up that often, but when its appropriate it can work well. I've got one huge "If monster" that I'm re-writing now. I'm hoping for a pretty dramatic simplification of this code.