kodecocodes / swift-style-guide

The official Swift style guide for Kodeco.
Other
13.11k stars 2.15k forks source link

Variable shadowing can lead to harder-to-read code. #75

Closed designatednerd closed 8 years ago

designatednerd commented 9 years ago

I think we should reconsider our position on using shadow variables when unwrapping. Our current argument is that the compiler will correct you if you try to use the wrong variable. I think there's a fundamental flaw in that argument that we've overlooked: It makes code significantly harder to read when not actually being compiled.

I see three major places where reading code like this outside of a compiler causes problems:

  1. Swift beginners. It's very confusing for two things with the same name to have such fundamentally different behavior. This is particularly true for those of us coming from Objective-C, where trying to define two things with the same name causes a compiler warning.
  2. Code reviews. For anyone who has to look through code before it gets merged down, there's an extra level of scrutiny required to make sure "wait, is this person trying to assign the optional or the unwrapped value?"
  3. Book editing. Are we expecting all of our book editors to take not just the sample code but any code typed in the book whatsoever and ensure that it compiles?

Take this example:

func createNewPerson(name: String?) -> Person {
  let person = Person()
  if let name = name {
    person.name = name
  }
 return person
}

Without compiling that code, how do I know that the name variable being set is the unwrapped one rather than the passed-in optional?

This is what I've been doing in my Swift code in a similar situation:

func createNewPerson(name: String?) -> Person {
  let person = Person()
  if let unwrappedName = name {
    person.name = unwrappedName
  }
 return person
}

For me, simply adding unwrapped to the unwrapped variable name makes the flow of the code significantly clearer. We don't have to use that exact syntax (especially since it makes variable names so much longer), but I do think we should reconsider shadowing variables for this reason.

OK, now that I've made my case: FIGHT FIGHT FIGHT FIGHT FIGHT!

1tpffru

cwagdev commented 9 years ago

For me, seeing ! during code review is the red flag. That's when I start asking "Why didn't we optionally bind this thing?"

I worry about "unwrapped" being too long of a prefix for the books, it's super difficult to keep line wrapping to a minimum, and that is a lot of horizontal space used up. Line wrapping of code in the books is arguably worse as far as readability goes IMO. Of course this example would not suffer from line wrapping, but throw in a Cocoa API call and it would be a different story.

My stance on shadowing will probably vary case by case and day by day though :]

hollance commented 9 years ago

Without compiling that code, how do I know that the name variable being set is the unwrapped one rather than the passed-in optional?

Because that's how Swift works. If you're inside an if let scope, it uses the unwrapped version. There is no ambiguity here.

designatednerd commented 9 years ago

@hollance: There's no ambiguity to someone who knows Swift well, and there's no ambiguity when it's compiled. To someone who knows it less well (for instance: me) and is trying to read the code without checking it out and compiling it, variable shadowing makes the flow significantly less obvious.

@cwagdev Maybe prefix with just a u instead of unwrapped? At work our Android code standards require any variable passed in as an argument to have a as the prefix, and this has been a pretty solid compromise between clarity and variable length.

hollance commented 9 years ago

I guess this is where semantic code highlighting comes in handy. ;-)

By the way, did you know that if var also works for unwrapping?

  if var name = name {
    name += ", Esq."
    person.name = name
  }
designatednerd commented 9 years ago

@hollance It certainly helps if the optional you're unwrapping is a local property, but doesn't do much if it's a passed-in argument.

As for if var: https://www.youtube.com/watch?v=Cd7tEsAuspA - thanks!

hollance commented 9 years ago

The thing about semantic highlighting vs regular syntax highlighting is that it gives each variable its own color. So you'd be able to see at a glance that the unwrapped name is different from the parameter name.

There is an Xcode plug-in for this, but I don't know if it works with Swift. https://github.com/kolinkrewinkel/Polychromatic

ColinEberhardt commented 9 years ago

I strongly oppose introducing any new variable or constant prefixes. Hungarian Notation has almost died out, there's no good reason to revive it!

I agree that we should rely on development tools to give hints regarding scope and context.

designatednerd commented 9 years ago

I certainly get the argument that within a compiled environment, this isn't necessary - that's why this rule was adopted in the first place.

The problem is that our code is often read outside a compiled environment, whether on the website or in books. This process becomes significantly less obvious without the aid of the compiler barfing if you try to use the wrong non-optional vs. optional var or let.

@ColinEberhardt and @hollance: Do you have any other suggestions on how we can give better context to these things in a read-only environment without resorting to a variable prefix? Or should we just say, "Screw it, type it into a playground if you don't get it."?

jawwad commented 9 years ago

+1 for using the same name. In general I would oppose variable shadowing but in this case I believe it makes sense. It makes things simpler since creative names or prefixes don't have to be used. Also the shadowed variable really refers to the same variable, just the unwrapped version of it, i.e. its not really shadowing any other variable, its shadowing itself.

gregheo commented 9 years ago

I like @jawwad's explanation that the shadowing is really "shadowing itself", so using the same name makes sense.

You could also argue the other side: if you had foo and unwrappedFoo and then you called some method on unwrappedFoo, it's possible people could be confused that these are two different things and that foo wasn't touched. They'll understand it eventually when they understand optionals, but that's the case for the shadowing way too.

Either way, we'll need to rely on people "getting it" eventually and if that's the case – I'd rather optimize for the eventuality of people understanding optionals.

Let's keep the discussion going though! Maybe we can beat the post count for the Objective-C style guide discussion on ivars vs properties!

cwagdev commented 9 years ago

A potential storm brewing from the God himself, https://devforums.apple.com/message/1127586#1127586

designatednerd commented 9 years ago

I think that post is more around whether variable shadowing should throw a warning - Laettener says no and that there's no specific guidance on whether you should shadow stylistically or not.

Since I started this fire, allow me to give an update: I've been doing more work in Swift on a prototype, and I still feel like I have a significantly harder time reading code without the shadows.

What it comes down to for me is this old chestnut:

Programs must be written for people to read, 
and only incidentally for machines to execute.

Yes, the compiler will tell you when you're screwing up when you're actually writing the code. But reading through others' (or even my own) code becomes significantly harder for me with variable shadowing. I have a much harder time intuiting where someone is accessing the unwrapped value vs. the wrapped value when I'm reading shadowed code.

I've also been working with newer programmers who have a lot of trouble understanding scoping (which I certainly did when I was starting out), and I only see this problem being made a thousand times worse by variable shadowing. How can a newer programmer tell that they're actually accessing an unwrapped constant in a particular scope rather than the original optional variable?

Having a clear delineation between the thing you are unwrapping and the thing that is unwrapped that is easily readable will make it far, far easier for noobs to tell which of the two they're accessing.

And I know it will make @ColinEberhardt very sad to hear, but I've adopted the u prefix on my personal code and I've found it helps tremendously.

JRG-Developer commented 9 years ago

I don't think Laettener gives advice one way or another here... he just says there won't be a warning.

It sounds like he's saying "do it if really you want to" to me... :]

cwagdev commented 9 years ago

Agreed, I was primarily being sarcastic and the thread reminded me of this :smile:

I am still in the shadow camp but can see @designatednerd's point.

aminiz commented 9 years ago

@designatednerd Shadowing is just neater. It is a readily deducible fact that inside the if let scope, you are referring to the unwrapped version. Explicit unwrapping is so common that anyone worth a few days of experience with Swift likely already knows this.

designatednerd commented 9 years ago

@annehiro I disagree with the assessment "anyone worth a few days of experience with Swift likely already knows this." Anyone with a few days of experience with swift and lots of other programming experience will likely get it, because they understand scoping. I went into detail above on why I think this makes it harder for n00bs who don't really get scoping to understand.

However, @gregheo, I don't know if this issue is worth leaving open anymore. While I still feel exactly as the title of this issue says (and am still using non-shadowed variables in my own code), making this change at a site level would require an insane amount of work updating the site and books at this point.

This set of code standards is used as a reference for a lot of workplaces still developing their own Swift code standards (including mine), but ultimately, these are for our site and books. And I think the horse has left the barn and run across half the country at this point on changing tutorials and books. :horse_racing:

cwagdev commented 9 years ago

FWIW I've written quite a bit of Swift since this came about and I'd say that I shadow 90% of the time, I skip shadowing is when the variable is extremely verbose.

rayfix commented 8 years ago

This is a tough one as there are good points on both sides of the argument. Personally, I don't like the term "shadowing" as it really is a static type transformation from optional to non-optional. I have never had trouble reading the code as it is easy to tell if something is optional or non-optional at the call site. Thinking of it as a type transformation also dovetails well with using guard to satisfy preconditions early and exit if not met. I hope that someday in the future we get syntactic sugar that looks something like this:

func compute(a: Int?, b: Int?) -> {
   unwrap a, b else { return }
   // compute with a and b Ints
}

Unwrap would also work with the mythical Result type.

There is often times when it makes sense not to use the same name but an abbreviated form. For example, coming from the Siesta framework (which I consider really great code modulo its funny whitespace):

guard let cache = config.persistentCache else { return }

Other times it just shadows.

I think @cwagdev is getting at. A few other projects I checked used a mix of shadowing and non-shadowing including Almofire, and Apple's open source implementation of Foundation. None of these projects, however, used Hungarian notation.

Based on these findings and my own personal gut I am going to rule that for now no standard should be made about to shadow or not to shadow. Closing this issue but we can revisit it again if the landscape changes.

JRG-Developer commented 8 years ago

@rayfix 👍 I'm also for closing this issue.

However, regarding

I am going to rule that for now no standard should be made about to shadow or not to shadow.

Actually, the style guide already has a ruling on this:

For optional binding, shadow the original name when appropriate rather than using names like unwrappedView or actualLabel.

When this issue was opened, there wasn't a lot of our code or tutorials that used "shadowing."

Now, however, is a different story:

At this point, I think we're committed to it... Personally, I've grown to like it too...

Like fine 🍷, it grows on you? 😉

rayfix commented 8 years ago

To me that says, "don't use Hungarian notation" which I am totally on board with. The "when appropriate" gives a little wiggle room for things like:

guard let json = content as? NSDictionary else { throw InvalidJSON }
designatednerd commented 8 years ago

FWIW, I stopped using the u and just wound up using different variable names, but I do realize I've lost this battle. :smile:

rvail2 commented 8 years ago

@designatednerd as someone new to swift, I think you were absolutely correct in this. It is confusing for beginners. I was really confused on how the scoping worked with this.