Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
Well, I intended to spend the last three weeks blogging about C# design process in anticipation of our announcements at the PDC, and then I got crazy busy at work and never managed to do so!
As I'm sure you know by now, we have announced the existence and feature set of that hitherto hypothetical language C# 4.0. Of course I'll be blogging extensively about that over the next year; today though I want to pick up where I left off last time and talk about the tragedy of Object Happiness Disease.
OHD is a disorder believed to be related to Thread Happiness Disease, the belief that threads are awesome and that you should therefore make as many as you possibly can. OHD is an even more common condition which afflicts developers at all levels of experience. Symptoms of OHD in C# developers include:
In short, the Object Happy believe that object-oriented programming is a good in of itself and that OOP principles are objectively (ha ha) good principles.
I don't share these beliefs.
Now, don't get me wrong. I believe that OOP is frequently useful. In fact, it is frequently the best available tool for a great many real-world jobs. OOP works particularly well when you have:
And that is where its goodness comes from: OOP techniques make real people more productive in solving real problems and thereby creating value. The techniques are not good in of themselves, they're good because they're practical.
Perhaps surprisingly, our goal in making C# is not to make an object-oriented language. Our goal is to make a compelling and practical language for general-purpose application development on our platforms, and thereby enable our customers to be successful. Making an object-oriented language called C# is just a means to that end, not an end in of itself.
So, yes, the oft-heard criticism that "extension methods are not object-oriented" is entirely correct, but also rather irrelevant. Extension methods certainly are not object-oriented. They put the code that manipulates the data far away from the code that declares the data, they cannot break encapsulation and talk to the private state of the objects they appear to be methods on, they do not play well with inheritance, and so on. They're procedural programming in a convenient object-oriented dress.
They're also incredibly convenient and make LINQ possible, which is why we added them. The fact that they do not conform to some philosophical ideal of what makes an object-oriented language was not really much of a factor in that decision.
Here's another way to think about it.
I had a discussion with Jim Miller a while back about the meaning of the term "functional language" in the context of Scheme. To paraphrase somewhat, Jim's position was along the lines that a "functional language" is one in which the design of the language makes it easy to program in a functional style and difficult (or impossible!) to program in a non-functional style. For example, Scheme does allow mutation of shared state -- a very non-functional-style feature -- but the convention in Scheme is to call attention to the fact that you are doing so by marking mutating functions with a "!". It is possible to program in an object-oriented style in Scheme, but the language resists you at every turn.
I certainly see the merits of Jim's position, but I take a weaker stance. When I say "functional language", I mean a language in which it is possible without undue difficulty to program in a functional style. I do not require the language to work against you if you try to program in a non-functional style in it. For example, I would call JScript a functional language. It is certainly possible to program in a non-functional style in JScript, but you can also treat JScript as Scheme with a slightly goofy syntax if you really want to.
I think about "object-oriented language" the same way; C# is an object-oriented language not because every feature of the language strictly adheres to OO philosophy. Rather, because as a practical matter it is possible to program in an OO style in C# if that's what you want. Adding features that enable other styles of programming does not make C# less of an OO language.
Next time, I'll talk a bit about the "theme" of C# 4.0 and how that influenced the design process. (And if you absolutely positively cannot wait to hear about some of the new language feature in detail, try my colleague Chris Burrows' blog.)
I would really appreciate if one of you C# 4.0 gurus could take a glance at the description of new features on Wikipedia: http://en.wikipedia.org/wiki/C_Sharp_(programming_language)#Future_development - and tell if there are some glaring mistakes there that have to be fixed for the sake of encyclopedic accuracy.
There are no glaring mistakes at first glance; there are a couple of very minor errors here and there.
Send me your email address and when I have a moment I'll go over the article in detail and email you my errata.
Thank you very much!
Oh, and regarding language features - apparently, there are some puzzlers regarding dark corners of variant generics coming up already (stuff you've covered here previously, but with no definite conclusion then):
"writing interfaces only intended to be implemented by one class"
Give me a built in way to mock a class (without resorting to typemock) that doesn't involve creating an interface/abstract base class, and I will gladly stop. Bonus point if you can show me this while implementing a MVP pattern (testing the Presenter).
Eric, I suffer from OHD, at least when it comes to the Interface portion for two reasons: Testability and build time abstraction
The first is a result of needing the interface so that i can mock my dependencies for unit testing. Can't really see a way around that. True, there will always exist at least two implementations of any interface, but it's still sort of artificial. I guess i'd put extension methods in a similar camp, since unless i attach the extension on an interface, i once again can't test it in isolation.
The latter is a result of having to recompile the entire tree of depdendent assemblies if a low level class changes even if the change is internal to the class. By keeping everything behind interfaces and the interfaces in a separate assembly, the build dependence goes away, which on large projects is a signficant time saving.
Any way around these limitations without going object and interface happy?
I think maybe I was not entirely clear on that point.
What I intended to get across was that I have seen codebases where every class definition also had an identical interface definition _for no good reason_.
Someone had been taught at some point that interface abstraction was A Very Good Thing, and so every time they defined a class, boom, there was a parallel interface right there too. And of course the code was then littered with casts casting values of interface types back to their corresponding class types, because after all, we know that every IFoo is actually a CFoo!
If you're writing interfaces that are designed to only be implemented by one class, and you have a really good reason for that, then that's not Object Happiness, that's working around the limitations of a tool in a fairly sensible manner.
Can you not declare that virtual which you want to mock?
Anyway, thanks for that post, makes me a lot more relaxed about C#'s future! Down with philosophy! Up with pragmatism! (Only a few centuries ago, philosophers were practicians...)
doesn't "makes it easy to program in a ... style" sometimes imply "and difficult (or impossible!) to program in a non-... style"?
i.e. when you want to be able to programm e.g. in oo style, you have the implicit requirement, that the things you're using (frameworks, apis, librarys) are also written in that style. otherwise it gets really difficult to use all that cool stuff that's already built in the framework while sticking to the chosen style.
I enjoy the mixture of styles in c#/.net (and love the new declarative style), but imho, contrary to your point, it makes the language less object oriented. ok, it's more the framework that is less oo, but those two things go hand in hand somehow.
"Can you not declare that virtual which you want to mock?"
You definitely can work around these things, but I'd imagine a lot of people find it a good bit more convenient & clear when working with interfaces, as you can guarantee that a stub/mock/testdouble/whatever won't have any kind of unwanted logic or dependencies. At work I've had a few cases where someone else's code is working off a base class and, due to things like static methods lurking within (which potentially each pull in dependencies of their own!) I've found it hard to get the tests working without a lot of aggravation. The moment they use an interface, things go a lot more smoothly. By implementing the interface in a base class, you get the best of both worlds. Same code, just an extra interface to worry about.
Like the article says, it's possible to go too far, though. I tend to make an interface for anything that has a tangible effect on my ability to test. If I am never going to need multiple versions of a class, and that class has few dependencies and little/no impact on my testing, then I don't bother. Same deal if there is no real 'common' interface and casting would be required.
Rule of thumb is simply what the article said. Don't do it just to do it. Do it because it greases the wheels and makes your job easier. Also, it helps to extract interfaces once the code is fairly stable, otherwise you just end up constantly updating code in two or three places instead of one or two).
Thank you for the clarification in the comments about why you wrote your article. On first read, it did sound like you were discounting OO principles out of hand. And you're right, there are some programmers who will take OO principles and apply them dogmatically and unthinkingly everywhere, just like there will always be people who will dogmatically adhere to the tenets of <insert your favorite religious or political organization>, and just like there will be people who will walk away from your article proclaiming that object-oriented programming is bad. There is still no good substitute for thinking.
No offense meant, but anyone who says that Scheme makes it difficult to program in an object-oriented style simply doesn't know squat about Scheme. The language does not "resist you at every turn". In fact, the language features (closures, macros) make it very easy for an average Scheme programmer to write his/her own OO system if he/she wants to. In fact, this is the real problem with OOP in Scheme: it's so easy to write an OO system that everyone writes a different one. Check out http://www.plt-scheme.org for a Scheme implementation that has a full-featured object system.
I agree with Thomas, and I'll go one step further and say that it applies even more so to functional programming. One of the big advantages you get from programming functionally are the guarantees of immutability, referential transparency etc, so if you're interfacing with code written in a non-functional style then being able to write your own code in a functional style isn't going to buy you as much as it would in a purely-functional program.
> # writing interfaces only intended to be implemented by one class
> # designing every class to enable derivation, whether it needs it or not
All well and good, except when it comes to testability. To get great test coverage, it is necessary to mock out objects. Ultimately, the only testability feature built into C# is friend assemblies. I wish that C# was testable without having to use the above features, but it's not. Misko Hevery (testability advocate at Google) says this well in several posts on his blog:
I guess the biggest bugbear from the DI (IoC) meme is that class-based OO languages need to split the .ctor in two, into an object graph constructor and an initialization constructor.
> I wish that C# was testable without having to use the above features, but it's not.
TypeMock to the rescue!
... except that some TDD proponents argue against TypeMock because it "promotes bad design" (i.e., does not force you to make an interface for every class, and forcibly decouple things even where it's pointless for any purpose but unit testing). So, it's not quite so simple.