TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Swift: When Unused Code Is a Bug

137 点作者 ileitch将近 7 年前

21 条评论

mpweiher将近 7 年前
Wow, Heisen-Swift, where the Heisenbugs are in the language specification!<p>What an object is, which is roughly equivalent to its observed behavior, should never depend on how it is declared.<p><pre><code> let greeter = LazyGreeter() let greeter1: Greeter = greeter print(greeter) print(greeter1) greeter.greet() greeter1.greet() </code></pre> UPDATE: just in case it&#x27;s not clear, this prints the following:<p><pre><code> greeter.LazyGreeter greeter.LazyGreeter sup Hello, World! </code></pre> So the same object responds differently to the same message, depending on how it is declared. Yikes!
评论 #17692265 未加载
评论 #17693105 未加载
评论 #17693154 未加载
评论 #17695022 未加载
ljm将近 7 年前
I&#x27;m reading the comments here about how the languages fails, or what it should have done better, but at the same time I&#x27;m feeling the same as I do when building up something with Kubernetes and the problem isn&#x27;t really the language, it&#x27;s the shortcuts we take with it to stay concise.<p>e.g. in Kubernetes you can give a Pod, Deployment, Service, Ingress, Volume, etc. etc. the exact same name and in fact many examples encourage that (because they are namespaced on the type). However this is practically just an artefact of a previous choice and when you&#x27;re looking at your YAML files you may not immediately know that distinction unless you have some familiarity with the tech.<p>What it says is that you can call everything the exact same thing. And it will work, and it will seem nice or elegant.<p>So it is with this Swift example and having a protocol, an extension, and a base class all declaring the exact same thing. Even if the compiler figures it out, how would you without reading the spec or knowing where those files live (because in the real world they would be spread across the filesystem)? It&#x27;s just poor consideration for comprehension.
评论 #17693109 未加载
评论 #17693504 未加载
mgoblu3将近 7 年前
Default implementations on protocols in Swift definitely can be a little dangerous. We&#x27;ve shied our team away from doing this unless the protocol is explicitly used as a mix-in type.<p>There&#x27;s been some proposals around fixing these, one that comes to mind is: <a href="https:&#x2F;&#x2F;forums.swift.org&#x2F;t&#x2F;introducing-role-keywords-to-reduce-hard-to-find-bugs&#x2F;6113" rel="nofollow">https:&#x2F;&#x2F;forums.swift.org&#x2F;t&#x2F;introducing-role-keywords-to-redu...</a>
评论 #17692239 未加载
lazulicurio将近 7 年前
Coming from the .NET world, where extension methods are just syntactic sugar for static methods, the &quot;bug&quot; described in the article wasn&#x27;t surprising to me. However, it did make me stop and think about how the example would look in C#. This was what I came up with:<p><pre><code> public interface IGreeter { } public static class GreeterExtensions { public void Greet(this IGreeter greeter) { &#x2F;&#x2F; If you have CA turned on, &#x2F;&#x2F; you&#x27;ll get a warning for not &#x2F;&#x2F; using the parameter &quot;greeter&quot; Console.WriteLine(&quot;Hello, World!&quot;); } } public class BaseGreeter : IGreeter { public abstract void Greet(); } public class LazyGreeter : BaseGreeter { public override void Greet() { Console.WriteLine(&quot;sup&quot;); } } IGreeter greeter = new LazyGreeter(); &#x2F;&#x2F; This has to be GreeterExtensions.Greet(greeter), &#x2F;&#x2F; because IGreeter is an empty interface greeter.Greet(); </code></pre> I think the syntax in C# makes it clearer that you&#x27;re doing something funky mixing inheritance with extension methods. If you wanted to do it the &quot;correct&quot; way, I think it would look something like:<p><pre><code> public interface IGreeter { void Greet(); } public class BaseGreeter : IGreeter { &#x2F;&#x2F; Edit: abstract method wouldn&#x27;t be equivalent to example &#x2F;&#x2F; public abstract void Greet(); public virtual void Greet() { Console.WriteLine(&quot;Hello, World!&quot;); } } public class LazyGreeter : BaseGreeter { public override void Greet() { Console.WriteLine(&quot;sup&quot;); } } IGreeter greeter = new LazyGreeter(); greeter.Greet(); </code></pre> Which is more complicated than the idiomatic way of providing default behavior with inheritance:<p><pre><code> public class Greeter { public virtual void Greet() { Console.WriteLine(&quot;Hello, World!&quot;); } } public class LazyGreeter : Greeter { public override void Greet() { Console.WriteLine(&quot;sup&quot;); } } Greeter greeter = new LazyGreeter(); greeter.Greet(); </code></pre> I&#x27;m not a huge fan of the Swift syntax; specifically, IMO, the declaration `class BaseGreeter: Greeter {}` hides where the implementation of `greet` is coming from. Without the method declaration it looks like it&#x27;s being inherited from the protocol extension, when it&#x27;s really not.
评论 #17692639 未加载
joeblau将近 7 年前
If I saw this in a code review, I would flag it for re-design. Mixing composition and inheritance especially in this way is definitely going to confuse anyone who doesn&#x27;t read every line to figure out what&#x27;s going on.
评论 #17692000 未加载
jordansmithnz将近 7 年前
In general I’ve found protocol extensions to be an incredibly powerful concept. There’s a couple of WWDC talks about ‘protocol oriented programming’ that highlight some great, practical use cases.<p>I’ve run into the author’s problem a few times, and I’d like to think that this is just a bug or behaviour detail that a future Swift version could correct. Swift hasn’t quite matured fully yet, and this is a great example of that (these examples are becoming fewer every year though).
jankins将近 7 年前
This is my favorite Swift gotcha :D<p>This bug bit me one time back before remote debugging came out when I was developing an app for a device that plugged into the lightning port. The only way to debug was to send log messages over the network to a log server running on my laptop, and the connected device sent messages to satellites where there was often a 10-minute gap in connectivity. And I couldn’t reproduce it in tests. That took some time to diagnose!<p>I like to bring this issue up if an interview candidate seems extremely bought in to protocol-driven development. I wouldn’t be surprised if any level of dev wasn’t aware of it, but would be impressed if anybody identified it.
dtech将近 7 年前
Can anyone explain to me what the rationale for this is?<p>I can understand the advantage of using static dispatch, i.e. Greeter#greet(object), but I assume there is some mechanism that avoids calling Greeter#greet if BaseGreeter would implement greet and call BaseGreeter#greet(object) instead.<p>Why does extending a class which extends a protocol not make the extending class implement that protocol in Swift?
wiradikusuma将近 7 年前
I wonder if this behavior is working as intended. It does look like a bug to the language itself, isn&#x27;t?
评论 #17691588 未加载
评论 #17691579 未加载
Buge将近 7 年前
It&#x27;s good that Periphery catches the unused code.<p>But if that&#x27;s the only reason it catches the problem, I think it could have room for improvement. For example in one place the error could exist, but at a different place, the real function could be called because the variable is declared as a LazyGreeter. Then Periphery wouldn&#x27;t catch the problem.<p>One way to fully catch it would be to give a warning if a base class implements a protocol but a derived class doesn&#x27;t. I&#x27;m not sure how many false positives this would have. Possibly you might only do this if the derived class tries to override a function from the base class&#x27;s protocol.
jolux将近 7 年前
This seems like a problem with using class inheritance more than anything else.
评论 #17692764 未加载
Chazprime将近 7 年前
I&#x27;ve run afoul of this a couple of times in my Swift delegate protocols. A really nice hack to implement Objective-C&#x27;s optional protocol methods in Swift is to use a protocol extension to create a default method that can come back to bite you when you subclass objects that conform to that protocol.
评论 #17692063 未加载
kitsunesoba将近 7 年前
Interesting. Swift has been my primary language for a couple of years now, but I’ve never run into this, mainly because I use subclasses far, far less often than I did under Objective-C. If this bug had existed in Obj-C back when It was still my primary language it likely would have bitten me.
delinka将近 7 年前
Providing a protocol with a default concrete implementation of the interface? That&#x27;s not passing code review here.<p>Would I have caught the <i>bug</i>? Probably not. But it looks like bad form to me to provide that default implementation to Greeter, so please rewrite your code.
评论 #17694595 未加载
mmfl将近 7 年前
If the concept is that protocol requirements represent the &quot;customisation points&quot; of a protocol, shouldn&#x27;t this apply equally to conforming classes and subclasses with inherited conformance? So I would be in favour of fixing this. Conversely, perhaps protocol extension methods etc not declared as protocol requirements could e.g. elicit a compiler warning when &quot;overridden&quot; by conforming classes&#x2F;subclasses with inherited conformance (since they are not intended as customisation points)? I think these changes would aid reasoning about protocols and reduce bugs.
didibus将近 7 年前
I don&#x27;t know that you can really call this a bug? It just seems to be the design choice. Though I agree, it seems odd at first, and appears possibly accidental, it could have been on purpose.<p>My question to swifters: what if LazyGreeter had another method, say lazyGreet, and you typed it as Greeter? Then you called let greeter : Greeter = LazyGreeter(); greeter.lazyGreet();<p>Would you expect this to work or fail?<p>I&#x27;d expect it to fail.<p>In that sense, I assume its simply that extension methods override child overrides. And its just something you need to know. That can even have interesting utility in some scenarios.
评论 #17693456 未加载
评论 #17694402 未加载
a_t48将近 7 年前
How much different is this from slicing in C++?<p>IE:<p><pre><code> #include &lt;iostream&gt; using namespace std; struct Greeter { void greet() { cout &lt;&lt; &quot;Hello, World!&quot; &lt;&lt; endl; } }; struct BaseGreeter: Greeter {}; struct LazyGreeter: BaseGreeter { void greet() { cout &lt;&lt; &quot;sup&quot; &lt;&lt; endl; } }; int main() { Greeter greeter = LazyGreeter(); greeter.greet(); &#x2F;&#x2F; Prints &quot;Hello, World&quot; return 0; }</code></pre>
评论 #17696139 未加载
sonnyblarney将近 7 年前
The explanation is counterintuitive and it&#x27;s one of those many Swift-ish things I&#x27;ve learned to be wary of. I find that Swift tries to be &#x27;all things&#x27; and to often I get lost trying to cobble together a mental picture of what a class is supposed to be given all the various ways to annotate it.
Legogris将近 7 年前
Another &quot;unused code bug&quot; would be when a locally scoped variable which is not supposed to be there shadows the intended global variable.<p>It&#x27;s just a matter of global scoping and precedence. But yeah, sharing notation between inheritance and extension is a tad bit reductionist.
untog将近 7 年前
I&#x27;m a little undecided on Swift extensions as a whole, and particularly protocol extensions. Maybe I&#x27;m just paranoid but it feels like it leads to a more complicated code layout.
评论 #17691747 未加载
wux将近 7 年前
This is an interesting demonstration of a hard edge of the language. It&#x27;s not because Swift holds some sort of grudge against dynamic dispatch, but rather because it&#x27;s trying to reconcile some subtly incompatible design goals:<p>1. Adding a default implementation for a protocol requirement shouldn&#x27;t stop existing types that correctly conform to that protocol from compiling. This is a reasonable user expectation and makes protocol composition a more powerful feature.<p>2. &quot;Retroactive conformance&quot; should be possible: someone should be able to conform a type they didn&#x27;t write to a protocol of their own creation. It&#x27;s a nifty feature that makes Swift extensions very powerful but also sometimes difficult to reason about.<p>3. Classes should inherit protocol conformances from their superclasses. As some of the Swift core team members have explained, this is not something that absolutely had to be the case, but it seemed sensible initially.<p>Points (1) and (2) preclude the language from requiring the `override` keyword for the subclass `LazyGreeter` to override a default implementation of a protocol requirement not itself overridden in the superclass `BaseGreeter`, since that would mean that (1) and&#x2F;or (2) would become impossible. Getting rid of (3) would remove the surprising behavior but it is rather late in the evolution of the language to make such a large change.<p>The way to reason about this behavior is to consider what are intended &quot;customization points&quot; in the language. It isn&#x27;t really covered in The Swift Programming Language, but (although not intuitive) it&#x27;s a fairly teachable concept:<p>A separate but similar issue exists with the distinction between default implementations of protocol requirements (methods declared in both a protocol and a protocol extension) versus extension methods (methods not declared in a protocol but found in a protocol extension). The former is dynamically dispatched and is considered a customization point for conforming types, whereas the latter can only be shadowed by conforming types but is not a customization point. As described here: <a href="https:&#x2F;&#x2F;nomothetis.svbtle.com&#x2F;the-ghost-of-swift-bugs-future" rel="nofollow">https:&#x2F;&#x2F;nomothetis.svbtle.com&#x2F;the-ghost-of-swift-bugs-future</a><p>(The Swift standard library actually uses this concept to its advantage. For instance, the inequality operator != is not a customization point for the Equatable protocol and is implemented in an extension method, guaranteeing that !(a == b) is always equivalent to (a != b) in the generic context.)<p>When implementing subclasses of a third-party open class, only open methods are customization points. In that case, Swift provides compile-time checking to prohibit overriding non-open public methods. This is possible because Swift does not support &quot;retroactive inheritance&quot; from a superclass as it does retroactive conformance to a protocol. Also, the language allows later versions of a third-party open class to stop your existing subclasses from compiling because of a conflict between a later-added superclass method and a pre-existing method in your subclass that now lacks the `override` keyword. In other words, the compiler-enforced prohibition is possible because points (1) and (2) above were not design goals for subclassing as they were for protocol conformance.<p>In the case illustrated by the present article, where protocols are used in class hierarchies, a third notion of customization points arises. As demonstrated here, a protocol requirement not overridden by a base class is not a customization point for a subclass. The protocol requirement can be shadowed by the subclass but not overridden. Consistent with the design goals enumerated above, this allows for the vendor of a library protocol to provide additional default implementations at a later point without breaking user code. (But if the vendor of a conforming superclass then implements an overriding implementation, your subclass would cease to compile just as it would for any other conflict between superclass methods and subclass methods.)
评论 #17693343 未加载