Design Corruption
A short time ago, I commented on the problematic decision to move from interfaces to base classes in the MS MVC framework. Hammett has left the following comment there:
Now its abstract class, then they will protected internal by default, and in the end people will get the usual MS tool, while this could be a break from the old habits.
Phil Haack, today (emphasis mine):
This is why there tends to be an emphasis on Abstract Base Classes as I mentioned before within the .NET Framework. Fortunately, when the ABC keeps most, if not all, methods abstract or virtual, then testability is not affected.
I think that this sums it up pretty well. And this is a major concern to me. Once you make design concessions, you end up with a corrupted design. And yes, I do think that the traditional, Framework Design Guidelines driven, design methods are broken. They lead to a design that is very hard to work with in many cases.
My objections remains the same, abstract base classes limits my options and make my life harder. I understand the difference between writing a framework and writing an application, make no mistake. But I think that the burden is on the framework developer.
There is a very simple, widely accepted, solution for this issue, have an interface in place, supply a base class for the people who want that, and move on with your life. The guarantee that you make is that if you use the base class, you wouldn't have breaking changes. If you are using the interface directly, that is your responsibility.
You know what, it works, very well.
Beside the point, if someone will give me a hook to the assembly loading stuff, I will patch the code on the fly to add the missing members.
Comments
Amen to that.
Always work with interfaces, supply ABCs for convenience.
Lets hope/push for a change back to interfaces.
It is kind of sad, I was hoping the ASP.NET MVC would break the Microsoft design trend and move in a better direction.
I would much rather use interfaces as well. However, Phil Haack did mention the solution above as a possible alternative, but decided against it because where would you add new functionality? If you add to the interface, you are creating a breaking change. If you add to the ABC, then it is no longer provided as a convenience mechanism because you are now forcing everyone to use the ABC to take advantage of the new functionality. I think there are definitely trade offs in either case.
You add it to both.
To the interface, because you need it, to the ABC, because you avoid the breaking change.
But by adding it to the interface, you are creating a breaking change to any client/third party libraries that may implement an alternate implementation of the interface.
And?
That is why you provide the ABC as well. They can either go with that or with the interface, if they go with the interface, they accept that potential issue.
More importantly, it ensure that you don't have magic stuff in the ABC that you can't access
Offering an ABC along with an interface solves nothing. What do you use for method parameters? The interface or the ABC? How do you expose the class say in a property? Do you expose the interface, or the ABC?
How can we add a property to the interface? That's a breaking change? That effectively creates a new interface.
So suppose I know there will be a new method added to HttpRequest, you'd rather we have IHttpRequest and then later IHttpRequest2? Rather than HttpRequestBase with all virtual/abstract methods?
Also, what problems specifically about HttpContextBase, HttpRequestBase, etc... do you think you'll run into?
I get a lot of pushback on this change, but I haven't heard much in the way of "I can't do blah because of this". I guess in part because the code hasn't been released yet. ;)
Phil,
Did you miss the part about the ABC being the cushion for the change?
Everything uses the interface. The ABC is there to prevent breaking changes from affecting implementing classes.
Adding a property to the interface is not a breaking change, because you add a virtual property to the ABC, which "fixes" all the clients.
Until we have the bits, and get to play around with them, you won't get the real issue.
Moreover, you will not get to those problems until people will use it with angry and fury.
The pushback on that is because this change goes back to traditional MS design, which we have seen, time and time again, to cause us problems to no end.
Here is a simple example of why this is making my life harder. It is not a good example, but it is simple.
Using IHttpContext, I can create a very simple (and very low performance) reverse proxy, by remoting the calls to another machine.
This requires inheriting from MarshalByRefObject, which I now can't do.
As I said, this is speculation, and probably bad design, but here is one scenario that is now blocked.
For that matter, allowing add in scenarios is an important concerns that I might want to handle. So I can handle an AddIn the ability to handle a request, so I need to remote that within my current AppDomain.
Can't do it with ABC.
@Phil,
"Offering an ABC along with an interface solves nothing."
For my concerns, it solves everything.
"What do you use for method parameters? The interface or the ABC?"
The interface. Always declare using the most abstract type.
"How do you expose the class say in a property? Do you expose the interface, or the ABC?"
The interface. Always declare using the most abstract type.
"How can we add a property to the interface?"
You can´t.
"That's a breaking change?"
Yes, it is.
"That effectively creates a new interface."
Correct. Now, wherein lies the problem? Or using MS argumentation tactics: I haven´t seen a lot of evidence for cases when it would be prudent to add members to published types. Anything acompanied by CanDoXXX members is not, to be sure...
/Mats
Phil, I guess I have to agree 100% with the other side here. There's such a lack of interfaces in the .NET framework that I'd have to say, the burden of proof is all yours. It's biting us all the time, and you'd make a lot of friends by breaking that habit.
BTW, mixins are a nice way to come around that problem. You can use a mixin to implement your interface, which could adapt to new interface members and add default implementations for them. Here's our take on it: http://www.re-motion.org (sorry for the advertising)
But basically I agree with Mats here, we're better off adding interfaces to classes than adding members to interfaces...
While following the various posts on this subject during recent days, there has been something tweaking me that I haven't quite been able to put a finger on until now:
WHY is the breaking change behaviour different between interfaces and ABCs in the .net framework? By intent? By happenstance?
This seems to be a question that no one so far has answered and yet I find myself getting to think that the problem here is that the entire discussion seems to be about selecting between two alternatives when in reality there are two schools of thought, each with a mutually exclusive option available.
Oren and friends (and I'm among them) want the lowest common denominator to be the interfaces specifically because they will break upon any change, and specifically because they are purely and unchangably abstract.
Phil and friends want the ABC instead specifically because they won't break upon any change, and specificalyl because they can slip functionality in as they change things.
Interfaces and ABCs are simply not available choices to these two groups. Oren and friends simply require interfaces. Phil and friends simply require ABCs. The requirements are just that much different!
The trick is this, and I sure hope Phil reads this: Both sets of needs can be served at the same time by, and get this, serving both needs at the same time. Go figure.
To Oren and friends: Any time Phil and friends rail against interfaces, perhaps we should demand that IEnumerable, ICollection, IList, etc. should all be deprecated in favour of abstract base classes. The argument holds just about as much water, after all.
Interfaces are good. Abstract base classes are good. Phil and friends could serve everyone's needs all at once, if only they could provide both and get over the need to pass the interfaces as arguments and return values instead of the ABCs.
Going back to my "WHY" question from earlier, and this one goes out to Oren and friends as food for thought: If the .net framework supported the definition of kind of abstract base class that is specifically abstract and may contain absolutely zero member implementations, in essence becoming and interface without the versioning issues, would you accept it? If it couldn't be enforced by the framework but could be promised by Phil and friends in some kind of "we promise that these ABCs will NEVER have any member implementations" statement that is published publically, would you accept that?
Where by "accept that" I of course mean "accept that in lieu of providing both interfaces and ABCs".
Also, apologies for the few typographical errors in my last comment. ;)
@Jeremy
"To Oren and friends: Any time Phil and friends rail against interfaces, perhaps we should demand that IEnumerable, ICollection, IList, etc. should all be deprecated in favour of abstract base classes. The argument holds just about as much water, after all."
Completely agreed.
"If it couldn't be enforced by the framework but could be promised by Phil and friends in some kind of "we promise that these ABCs will NEVER have any member implementations" statement that is published publically, would you accept that? "
Well of course I would break open the champagne at such a statement, but it wouldn't make me entirely happy unless I could also extend multiple such "pure ABCs" - essentially making them interfaces.
The problems with not using interfaces are manifold - lack of MI is one, trigger happy use of non-virtual members is another one (binding to implementations increases coupling), use of non-abstract members yet another one (ye good ole diamond problem) - the ability to "slip by" new members onto published types could potentially be seen as another one but only if abused by adding new members without sensible default implementations...ahem...having the default implementation throw a NotImplementedException is just an excellent indication that it is not a good candidate for a member that might be slipped onto a published type.
In contrast, a good candidate for such a member that could be added to a published type could be a new GetFullName() method on, say, a Person ABC implementing the IPerson interface. The new GetFullName() method would concatenate the (published) FirstName and LastName members from the IPerson interface.
Pretending for the while that the example in the .NET Framework Design Guideline chapter 4 didn't suggest doing just that no-no (adding a default implementation throwing NotImplementedException and adding a CanDoXXX member) I personally don't have much of a problem with the ability to add new members to base classes. It is not, so to speak, the very thing that I hope in particular to avoid by using interfaces, but on the other hand, as the example in the Design Guideline shows, it does have the drawback that it can be abused so I guess in the end I'd say that perhaps that is indeed another win for interfaces...
/Mats
@Mats - As long as GetFullName() is marked as virtual, I suppose I could live with the addition. That said, I am still most definitely in the "Oren and friends" camp on the overall issue. I just plain fail to see how one could agree with the existence of IList<T> and List<T> and yet rail against having both IHttpContext and HttpContextBase (or whatever it gets finalized as).
As a more general comment, it is worth pointing out that I have a great degree of empathy for Phil and the position in which he and his compatriots find themselves. They are the publically-known vanguards of a new era at MSFT, one promising openness, transparency, responsiveness, etc., yet as soon as they throw their work over the fence it will be bound by the exact same thing that makes most MSFT products bloat, rot, and suck over time, opening the doors for rapidly gaining upstarts like Linux and Apple: the fact that a high bar for back-compat largely kills flexibility, agility, and innovation.
I most certainly don't envy the position they are in. Not that that would stop me from fighting to have them do the right thing instead of side-stepping it (have you noticed that Phil is generally staying away from providing a real response to the request to provide both interfaces and ABCs?)
Ah, but if we ship an interface, I have to assume that someone will implement the interface directly. So changing the interface will still be a breaking change. I could hope that people didn't implement it, but I doubt I can convince people here to allow me to change an interface because I "know" nobody is implementing it directly.
I'm not railing against interfaces. Read my series on versioning carefully. I personally like interfaces. I'm merely pointing out there are situations as a framework developer in which ABCs provide benefits over interfaces in regards to versioning.
Believe me, I personally would love to introduce breaking changes. I'd break stuff left and right given a choice. ;) But I have these constraints of not breaking and this is my explanation of how ABCs help me with that.
Phil,
There is a term in Hebrew, it is Zabashu.
It means, roughly translated, that is HIS problem. With an emphatic that means that it is no concern of mine.
In English, I would call it consenting adults. If you choose to use the interface, you accept the responsibility of dealing with possible breaking changes.
I don't believe in coddling as a design methodology
@Ayende Unfortunately, it's my problem right now. If I ship an interface, I can't change it. I see what your saying. It seems your proposing a new Framework Design Guideline.
Provide an interface and ABC. If customers inherit the ABC, they won't get broken if we need to add a member. If they inherit the interface directly, they assume the risk of a breaking change.
I could go for that. The hard part is convincing everyone else. ;) As it stands, I can't simply just ship a break.
The reason is partly due to the fact that the HttpContextBase et all abstractions are not part of MVC. Routing is becoming a separate feature from MVC. We still have a lot of interfaces in MVC. We just happen to have introduced a few ABCs as well in specific cases where we thought the chance of changes was high.
It's rather amazing to me to be seeing this discussion. I figured the whole Interfaces good, Abstract base classes bad discussion was long behind us, but maybe I'm just hanging around in a specific sub-clique.
Breaking changes happen. If you're not willing to deal with them, don't go implementing the interface, but to take the chance to creating altnerative implementations away just because there is a burden created seems rather paternal.
Sorry, I don't buy into this approach.
And yes, I do believe that the Framework Design Guideline is wrong and misleading in several key points. Specifically, in regards to extensibility and usability of said frameworks.
I would go with this (Haacked statement):
"Provide an interface and ABC. If customers inherit the ABC, they won't get broken if we need to add a member. If they inherit the interface directly, they assume the risk of a breaking change."
That seems logical because we two bonuses at the sime time:
an ABC witch guarantess code won't break (more or less)
an interface, wich provides 100% extensibility
@Phil,
I understand and actually agree with the "no breaking changes" policy. My point is that:
1) interfaces have no problem with this policy
2) adding new members to published ABCs (_especially_ ones throwing NotImplementedExceptions and complemented by CanDoXXX members) is not an appropriate workaround for this policy.
The whole thing boils down to dynamic casting vs CanDoXXX methods. The way to release new functionality without introducing breaking changes using interfaces is by creating new interfaces and asking developers to use dynamic casting to access the new features.
The oft-cited "no breaking changes" policy doesn't really make any difference such that it forces you to go with ABCs before interfaces (although oddly enough the Framework Design Guidelines do seem to make this recommendation) - the question is more if you have a policy in place that would dictate the ABC aproach, such as a "new features must show up in IntelliSense" or even "developers can't be trusted with dynamic casting" policy? Are you bound by any such policy? Or is it really just the Framework Design Guidelines "prefer ABCs over IFaces" recommendation that pushes you in this direction?
/Mats
I don't know if Phil is or is not still reading this, but to both him and to the others reading this:
Did you notice how Phil has yet again sitestepped the point that providing both interfaces and ABCs would make both camps happy at minimal cost to Phil (and team)?
I hate to be rude, but there are occasions where the extra emphasis seems relevant, so: what is the big fucking deal with MSFT taking a little extra effort to do the right thing and satisfy both groups while doing harm to neither???
The only possible answer I can think of is that Phil (and team) don't want to have to expose the interface variants as arguments and return values. That had better be the answer because the only alternative is that this whole ASP.net MVC project is just designed to placate the masses who have just started getting curious about MVC while simultaneously being a big "Fuck You!" to the community members that have actually been clamoring for a MSFT-sanctioned MVC solution. You know the oines: they are the ones pleading for interfaces.
Phil has made it obvious: more words aren't going to help. As such, these will be the last from me on this subject. From here on out, the only things that actually matter are the actions of Phil (and team) and certainly not their words (which, as you might recall, involved a lot of hand-waving about listening to the community.)
@Mats
I'm thinking that extension methods can be used to introduce discoverability.
For example, lets say the original object implements IFoo. In the next version, there's new functionality added via an IBar interface.
Experienced developers will know they can cast the object to the IBar type. No help is needed.
For discoverability, though, there can be an extension method defined called "AsBar()" which will take the instance and cast it to IBar. Because the extension method will be in the same namespace where the new functionality will be available, it's in scope and immediately available via Intellisense.
@James,
I definitely agree - it seems like an utterly sensible and useful approach! Best of both worlds, not much of any actual drawbacks as far as I can see...except of course that it is only for .NET 3.5 and beyond, but going forward that shouldn't be a problem. Flexibility, extensibility, convenience and nice discoverability. In one word: wonderful.
/Mats
The thing is, shipping both an interface and ABC only helps implementers of that interface. It doesn't do a damn thing to help the callers, which is where the break happens!
Chris,
Ha?
Adding a method to an interface is a non breaking change to the caller.
Adding a method to an interface requires consumers of that interface to recompile.
In most orgs, a recompile triggers a new test cycle. Which triggers a new deployment cycle. Which costs MONEY!!!
That is why it's a breaking change.
Chris,
Adding a method to the interface does NOT require recompilation.
Try it:
ClassLibrary1:
public interface IFoo
/*
*/
ConsoleApplication1:
class Program
Compile and run.
Now remove the comments from the library, compile just the library code, manually copy to the app, and run.
It runs perfectly fine.
But if you want to tell me that you aren't going into a test cycle after changing the code in one of your dependent assembly, then I have to say that you are heading for trouble.
it sounds like Phil Haack is trying to please everyone (by making sure things won't break), which is impossible to do. interface with abc seems like best compromise in providing extensible and easy to use framework vs. making sure things don't break.
Comment preview