views:

683

answers:

14

Do you find the following C# code legible?

private bool CanExecuteAdd(string parameter) {
    return
        this.Script == null ? false
        : parameter == "Step" ? true
        : parameter == "Element" ? this.ElementSelectedInLibrary != null && this.SelectedStep != null
        : parameter == "Choice" ? this.SelectedElement != null
        : parameter == "Jump" ? this.SelectedStep != null
        : parameter == "Conditional jump" ? false
        : false.Throw("Unknown Add parameter {0} in XAML.".F(parameter));
}

where Throw is defined as:

public static T Throw<T>(this T ignored, string message) {
    throw new Exception(message);
}

I know that's not idiomatic C#. However, would you be able at understand it at first or second glance? Or did I stray too far?

+11  A: 

I've used this sort of code in Java a fair amount. I don't like the false.Throw bit, but having multiple conditionals like this (particularly formatted this way) is fine in my view.

It's slightly strange the very first time you see it, but after that it's just a handy pattern to know about.

One alternative to using false.Throw here would be something like this:

bool? ret = this.Script == null ? false
    : parameter == "Step" ? true
    : parameter == "Element" ? this.ElementSelectedInLibrary != null && this.SelectedStep != null
    : parameter == "Choice" ? this.SelectedElement != null
    : parameter == "Jump" ? this.SelectedStep != null
    : parameter == "Conditional jump" ? false
    : null;

if (ret == null)
{
    throw new ArgumentException(
       string.Format("Unknown Add parameter {0} in XAML.", parameter);
}
return ret.Value;

EDIT: Actually, in this case I wouldn't use either if/else or this pattern... I'd use switch/case. This can be very compact if you want:

if (this.Script == null)
{
    return false;
}
switch (parameter)
{
    case "Step": return true;
    case "Element": return this.ElementSelectedInLibrary != null && this.SelectedStep != null;
    case "Choice": return this.SelectedElement != null;
    case "Jump": return this.SelectedStep != null;
    default: throw new ArgumentException(
        string.Format("Unknown Add parameter {0} in XAML.", parameter);
}
Jon Skeet
I don't like it either, but have no better idea. I'd love to do following:: (someoption) ? fsdfsdf: throw new Exception("fsdfd");But obviously it doesn't compile.
Yes, that's exactly my problem. I wrote my comment before you updated your answer. :) Unfortunately, I found solution with null longer and thus less legible. What's more it's sort of less universal since in case of return being some class, null can have meaning, so we can't use it as a magic number.
@wwosik: That just goes to show that you need to use judgement on each situation separately :)
Jon Skeet
About switch version - my issues: 1. {this.Script == null} test is somewhat special. 2. "returns" everywhere :). They add additional text, which actually does nothing. 3. You can't mix == "text" conditions with other types of conditions.
Yes, the this.Script == null test is different to the rest, and the switch code makes it clearer that it's a different kind of condition. And yes, there are returns everywhere - but there aren't parameter comparisons everywhere. Either way you're repeating *something*. For your third point - if you're mixing a lot of different kinds of conditions, then you're unlikely to get clean code however you structure it - and trying to do it all in one expression isn't going to be terribly nice, IMO.
Jon Skeet
Heh, I just hate if(){}else if(){} else if() {} else {}, but introducing anything like strategy/polymorphism would be an overkill, never mind Dictionary<Predicate, Action>. That's why I was looking for a nicer way to express it.
BTW, a bonus question, how do you like "Unknown Add parameter {0} in XAML.".F(parameter) as an alias for string.Format. Reasoning behind is that I first think of a whole sentence to write out and then I realize that I want to put some data there. Also I prefer Func("abc{0}".F(1)) to Func(string.Format("abc{0}", 1).
@wwosnik: I dislike the ".F" which is why I changed it in my versions :)
Jon Skeet
;) Any reason for disliking it apart from not being standard? I somehow managed to convince my otherwise rather suspicious colleagues and they even started to use it themselves. But probably you're right, it's importing other languages into C# (Python this time).
@wwosik: "F" is completely undescriptive. If `string.Format` had been an instance method, that would have been okay - or if you could think of an alternative name which is still a full word rather than a single letter, I could be convinced.
Jon Skeet
@Jon, I was just typing the same thing, had .F changed to .Format, or .StringFormat, it would be legible
Chad
At first it was .Format indeed. Later I decided that F being not coincidentally the first letter of Format immediately conveys meaning to anyone who knows string.Format.
I actually had to read it twice... I saw the {0} and was looking for where string.Format was ...it was only on the second pass that I noticed the .F
Chad
+3  A: 

Being non-idiomatic means that you're forcing the reader to spend time thinking about whether or not what they're reading means what they think it means.

So being legible doesn't buy the sophisticated (namely, suspicious) reader very much. This strikes me as a case of being clever for the sake of being clever.

Is there any reason not to use a switch or an else if construct here?

Ori Pessach
+1 for the non-idiomatic code comment - not so sure about the else-if construct vs. switch or command pattern...
John Weldon
@John Weldon: Yes, a switch would be a better choice.
Ori Pessach
Yes, I know non-idiomatic is somewhat on a lost position and I noted that in my post. I'm really looking forward to find a nice solution to a if () {} else if() {} else if() {} else if() {} else {} since I find these additional characters as noise.
+5  A: 

I really don't like this code. It took me more than about 15 seconds to understand, so I gave up.

An if/then would be preferable.

David Morton
Maybe if one has some acquaintance with functional languages it is more clear. I really like F# Match construct and wanted to see if C# can match it.
@wwosik, writing functional code in C# is getting easier, but if it _feels_ like you're putting a square peg in to a round hole, then you probably are. It may pain you, but you're best served exploding this out in to a verbose but easy to understand set of if/else tests.
Michael Meadows
I understand functional languages very clearly. I've been working with F# for about two years now. That being said, I still find the ternary operator confusing in this usage. It just doesn't seem like it quite fits with C# here.
David Morton
A: 

I've actually never seen the ternary operator pushed out so far. However, I understood where you were going. Also, I agree with Jon, I don't like the false.Throw part.

Chris Lively
+9  A: 

I vote for non-legible.

Although the syntax is correct, it's somewhat convoluted and since it's not, dare I say, "traditional", many developers will have to waste time trying to ensure they understand what they're reading. Not an ideal situation.

Readability is definitely one key ingredient to good coding, and I would say your sample isn't immediately readable for most devs.

KP
I do imagine it's a bit odd at first, but my question is rather - can one understand and remember the concept behind it?
@wwosik: Being odd is almost always bad. It's a distraction and a drain on the reader's attention. Since you seem to care about readers of your code, you should care about their time.
Ori Pessach
+1  A: 

Why not use a nullable type bool?

private bool? CanExecuteAdd(string parameter) {
return
    this.Script == null ? false
    : parameter == "Step" ? true
    : parameter == "Element" ? this.ElementSelectedInLibrary != null && this.SelectedStep != null
    : parameter == "Choice" ? this.SelectedElement != null
    : parameter == "Jump" ? this.SelectedStep != null
    : parameter == "Conditional jump" ? false
    : null;

}

sylvanaar
1. For a bool it would work. For a reference object less so, since a null can have meaning.2. It requires additional tests for the null value + extracting value out of bool? making the code less terse.
+1  A: 

At first I was horrified, but actually I can't think of a way to write this much clearer in C# - I was trying to think of something where you had an array of Funcs mapped to results, but it got even uglier.

Even though parsing through the actual conditionals is rough, it's at least easy to grok the intent, though I'd prefer to use a switch block and handle everything else as a special case.

Paul Betts
Concept of Dictionary<Predicate, Func<T>> is sometimes quite useful, esp. if you need an extensible list of actions.
+4  A: 

Convert your nested ternary into a switch. Never coerce one control structure into doing poorly or illegibly what a built-in structure will do perfectly, especially if there's no obvious benefit.

Alison R.
+6  A: 

I like the conditional operator, and use it a lot.

This example is a little confusing, because the nature of the operator is not clear from the layout and usage.

At the very least I like to make the choice and alternatives clear by using this formatting:

choice
  ? true-case
  : false-case

But if we apply that to your code it reveals the lack of clarity in using the construct this way:

return
    this.Script == null 
                ? false 
                : parameter == "Step" 
                    ? true
                    : parameter == "Element" 
                        ? this.ElementSelectedInLibrary != null && this.SelectedStep != null
                        : parameter == "Choice" 
                            ? this.SelectedElement != null
                            : parameter == "Jump" 
                                ? this.SelectedStep != null
                                : parameter == "Conditional jump" 
                                        ? false
                                        : false.Throw("Unknown Add parameter {0} in XAML.".F(parameter));

This feels to me like we're trying to use the conditional operator like a switch statement, where a switch statement, or better yet a design pattern like the Command Pattern would be much clearer.

John Weldon
Well, with that formatting of yours, I would never use that construct. The whole thing lies in nice alignment of ? and :
@wwosik Yes, but that's my point... if you have to rely on clever (non-intuitive) formatting of the operator then it's a little suspect. Not a hard and fast rule, but in general.
John Weldon
I agree with wwosik: if you format something badly, it's bound to look bad. You could do the same thing with multiple if/else-if/else-if statements as well, and it would look just as bad. (And personally I find that this *is* the intuitive formatting of the operator - I would generally use this formatting rather than the "triple line" formatting.
Jon Skeet
@Jon Skeet - interesting... if you say so it must be :) It's just that with the OP formatting the condition and alternative cases are continually bleeding over to the next line, just to make the conditions line up... I've used my 3 line style of formatting a lot, and when used in (subjective I know) intuitive ways, it looks good and readable. :)
John Weldon
+1 for command pattern. My mantra: "more than one else should be a switch; more than four cases should be a list of command objects." Set matching would be nice, but C# doesn't provide the facility to do that.
Michael Meadows
I just wanted to note that alignment of my code in the question is automatically produced by Visual Studio (Ctrl+E,D). It's not like I had to adjust it.
@John @Michael - I'm aware of the Command pattern, but find it a bit too much of an overkill for a simple function.
@wwosik +1 for pushing the community discussion on the conditional operator... I've always liked that operator, and I like to see the community response to it.
John Weldon
Don't forget that lines are a lot shorter on SO than on a normal developer's IDE...
Jon Skeet
I would never ever put the question mark on a different line from the condition. Question mark implies a test. The condition is being tested, not the true-expression.
Ben Voigt
@Ben Voigt - fair enough.. it's pretty subjective... I prefer the formatting of the indented ? and : to indicate the two branches... it's an idiom I'm familiar with.
John Weldon
+25  A: 

Why not use a switch? I think it's way more readable.

private bool CanExecuteAdd(string parameter) {
    if (Script == null)
        return false;

    switch (parameter) {
        case "Step":
            return true;
        case "Element":
            return ElementSelectedInLibrary != null && SelectedStep != null;
        case "Choice":
            return SelectedElement != null;
        case "Jump":
            return SelectedStep != null;
        case "Conditional jump":
            return false;
        default:
            throw new Exception(string.Format("Unknown Add parameter {0} in XAML.", parameter));
    }
}
Fede
I like this. if/else is better than the conditional operators, but this just cries out for a switch.
ScottS
Yes, for strings it does the job. However, if you have some more complicated conditions it won't :(
I like using the right tool for the right job. IMO the switch statement is the best solution for this specific scenario. In another scenario I would definitely consider using the Command Pattern as suggested by @John Weldon
Fede
I actually think the original author's code is more "readable" in the sense that it's more compact and still makes sense, however a switch is more "readable" in the sense that it uses a well known and understood C# construct. Which is better? Sounds like the author really wants to be using F# instead.
Nick
+1  A: 

Too hard to read, take care of exceptions first.

Handle each case in it's own if, then you can have more complex conditions.

This is one of the few times, this many separate returns in a method would be acceptable

private bool CanExecuteAdd(string parameter) 
{       
    if (this.Script == null)
        return false;

    if (parameter.NotIn([] {"Step", "Element", "Choice", "Jump", "Conditional jump"})
        throw new Exception("Unknown Add parameter {0} in XAML.".F(parameter));

    if (parameter == "Step") 
        return true;

    if (parameter == "Element")
        this.ElementSelectedInLibrary != null && this.SelectedStep != null;

        // etc, etc
}

Oh, and the .NotIn is an extension method, the opposite of this, I would imagine (can't say this is quite exact to what is needed)

public static bool In<T>(this T obj, IEnumerable<T> arr)
{
    return arr.Contains(obj);
}
Chad
1. Can you really write \\ .NotIn([] {"Step", // ?2. Doesn't it instantiate an array on each call?
My syntax may be of, the point was to validate the parameter was in the list of valid values, if not, there's no point going through all the conditions just to learn that. You can use the In/NotIn and a constant for the array, or however you want to handle it
Chad
Well my issue is following: assume you're adding an aditional option - now you have to change your function twice. That's why I don't find it wins anything over plain if/else if/else if/else chain.
Well, sounds more like a design problem higher up. Too many strings. Make parameter an enum, or custom type, implement the command pattern. All the if/else/switch/ternary, etc just smells bad and indicates a flaw in the design somewhere.
Chad
+1  A: 

It looks fine to me, but I'd alter your Throw method to:

static TObject Throw<TObject, TException>(this TObject ignored, TException exception)
{
   throw exception;
}

That allows you to specify the kind of Exception thrown.

Robert Davis
Yes, my first version was actually like that.
+1  A: 

Unfortunately the ternary operator (?:) isn't a common idiom in the C languages--I've met many C, C++ and C# developers who have to pause to read it because they aren't familiar with it or don't use it. That does not make it a bad language feature or illegible, however those same developers may call OP's example illegible because it nests a language feature they're uncomfortable with.

I don't find the example illegible--I've seen nested ternary operators many times. However, I do feel that using a switch would be a preferable choice for checking 'parameter' against the strings.

Far more galling to me is that Throw extension method that ignores the 'this' parameter. What would 42.Throw(...) mean? If I were reviewing the code I would call it out as bad design.

Curt Nichols
That zero parameter (42) determines the return type so that it compiles. I'd prefer to write ": throw new Exception();" but I can't. But imagine var age = person is Mommy ? 32 : person is Daddy ? 33 : 42.Throw("I don't know them!");
It still smells like contortions necessary to make it fit an unfortunate design decision. :)
Curt Nichols
+5  A: 

My rule of thumb: use expressions for things with no side effects. Use statements for things with one side effect and for control flow.

Throwing is effectively a side effect; it does not compute a value, it alters control flow. You're computing a value, computing, computing, computing, and then boom, side effect. I find code like that confusing and vexing. I say that control flow should be in statements, not the side effect of something that looks like a computation.

Eric Lippert
Yes, I fully agree that such a computation should not have side effects. Note that other expressions do not have side effect.However, that exception should rather say "TOTAL FATAL ERROR. THIS SHOULD NEVER HAPPEN IN PRODUCTION". It's rather a sort of Assert.Fail() then control flow handling. The thing is the parameter comes from a CommandParameter of a XAML tag, and misseplls happen. Of course you could parse it to enum, but...
@wwosik: You bring up an excellent point. If something is actually *impossible* then it shouldn't be an exception; exceptions should be testable. It should be an assertion.
Eric Lippert
Tja, a jolly good idea. Wonder why I didn't go for Assert.Fail immediately. Nah, everyday I learn something new, event if I knew it before :)