System.TimeZone2 Naming … and related design guidelines


There is an interesting discussion on the BCL blog about a new BCL type called TimeZone2.  Just take a look at the comments below the System.TimeZone2 Starter Guide post. The new type supersedes an existing type called TimeZone (which is obsolete now).


Lots of people are not thrilled with the “2” suffix. I am responsible for the guidelines (excerpt below), and so I thought I would try to provide some context:


·         Some comments suggest that the BCL team just take a breaking change, i.e. remove the old type and add a new one with the same name. This is not an option for such a widely used framework. We basically have a policy against doing any intentional breaking changes. In other words, this is not negotiable 🙂  


·         Some comments suggested using a new namespace, e.g. System.Globablization.TimeZone. This would cause all sorts of problems. The main one is that most source files import System namespace and this approach would mean that most source files have to fully qualify the new TimeZone (otherwise you would get type name ambiguity). I don’t like fully qualifying core types when I program and usability studies we conducted have shown that I am not alone. There are other issues related to the difficulty in searching for documentation on such duplicated types, referring to such types in books and in speech, etc.


·         Using numeric suffixes is the last resort thing. If you have a “good name” that does not include a numeric suffix, you should use it. The problem is that sometimes all reasonable names are already taken, and that’s when the guideline is applicable. It’s what we call the best out of many bad alternatives.


Having said that, I would love the BCL team to find a new “good name” for the type. If you have a great name, post it to my or the BCL blog.


And for reference, here is the excerpt from the design guidelines:


Naming New Versions of Existing APIs.


Sometimes a new feature cannot be added to an existing type even though the type’s name implies that it is the best place for the new feature. In such case a new type needs to be added, often leaving the framework designer with a difficult task of finding a good new name for the new type. Similarly, often an existing member cannot be extended or overloaded to provide additional functionality and a member with a new name needs to be added. The following guidelines describe how to choose names for new types and members that supersede or replace existing types or members.


þ Do use a name similar to the old API when creating new versions of an existing API.


This helps to highlight the relationship between the APIs.


class AppDomain {


    [Obsolete(“AppDomain.SetCachePath has been deprecated. Please use  AppDomainSetup.CachePath instead.”)]


    public void SetCachePath(String path) { /* … */ }


}


 


class AppDomainSetup {


    public string CachePath { get { /* … */ }; set  { /* … */ }; }


}


þ Do prefer adding a suffix rather than a prefix, in order to indicate a new version of an existing API.


This will assist discovery when browsing documentation, or using Intellisense. The old version of the API will be organized close to the new APIs as most browsers and the Intellisense show identifiers in alphabetical order.


þ Consider using a brand new, but meaningful identifier, instead of adding a suffix or a prefix.


þ Do use a numeric suffix to indicate a new version of an existing API, if the existing name of the API is the only name that makes sense (i.e. it is an industry standard), and adding any meaningful suffix (or changing the name) is not an appropriate option.


// old API


[Obsolete(“This type is obsolete. Please use the new version of the same class, X509Certificate2.”)]


public class X509Certificate { /* … */ }


// new API


public class X509Certificate2 { /* … */ }


 


 

Comments (30)

  1. Kathy Kam says:

    Yeah.. Krzysztof have finally blogged about the controversal naming guidelines around TimeZone2! Check

  2. Haacked says:

    How about TimeZoneRegion?

  3. Right now, there is no easy way to convert a time from one arbitrary timezone to another arbitrary timezone

  4. I remember the highlighted paragraph from the (excellent) FDG book (which I own and recommend as one of the first books to read before starting to design applications in .NET) as one of the very few paragraph I wholeheartedly disagreed with.

    I’m with the first camp that calls ‘Break! Break!’ and I think that this is the best option, considering how .NET’s handling of assemblies and deprecation by ObsoleteAttribute is supposed to work.
    Walking on your tippy-toes in _every_ situation in an attempt not to break _anything_ would, a few years down the line, result in an unusable patch-work. Some things you can’t see down the line and sometimes you have to get back to the drawing-board to re-define something you though would never change.

    Think about someone new to the framework 3.0 who encounters TimeZone and TimeZone2. That just seems ridiculous.

  5. Totally cheap. .NET has jumped the shark. I hate the new name.

  6. Henry Boehlert says:

    Since Kathy’s article mentions a relationship to the new Vista API and looking at her example #4, TimeZoneInformation sounds like a feasible alternative to me.
    However, Information is probably one of your black-listed suffixes, isn’t it?

  7. Micael Baerens says:

    I’m one of the people who wrote a comment on making it a breaking change.

    I don’t argue that making a breaking change will hurt some people, but I think this is an investment in the framework in the long run. If we look ahead 10 years, will .NET be littered with TimeZone9 and X509Certificate6? Will we still want to use it?

    You say that you cannot introduce breaking changes due to policy, yet .NET 2.0 introduced many breaking changes? So has the policy changed – are you saying that there won’t be a single breaking change in .NET 3.0?

    I think you should have the courage to realize that this /should/ be a breaking change, and that you will be better off taking the hit now, than letting developers suffer for it in the many years to come.

  8. Damien Guard says:

    If Microsoft are going to choose backwards compatibility over a clear sensible evolving API we are going to be in the same scenario as Win32 is now within a few years.

    I’d go with giving the new class the TimeZone name and moving the old one to a .Obsolete namespace (for 1 version only) and adding the Obsolete attribute to it.

    The VS 200x project upgrade wizard should detect references to the original TimeZone class and switch them to System.Globalization.Obsolete.

    If Microsoft aren’t prepared to break the underlying API between major revisions .NET is going be become a mess real quick.

    Where they want to support an old version of a class for some time then an old-API wrapper round the replacement class might also be an option.

    [)amien

  9. We recognize the problem of frameworks deteriorating over time and we look for ways to stop or reverse this process – but without resorting to breaking changes. For example, in 2.0 we added type forwarders which we plan to use to fix some of the layering/dependency issues. We are also thinking about technologies that would allow us to fix naming and design issues without resorting to breaking changes.

    Breaking changes are just too disruptive for many projects/customers and they almost never add enough value to be worth the pain. There are cases where we feel like the benefits outweigh the costs (for example changes required to fix security issues) and we do approve limited breaking changes in such cases. But, I would assert naming changes for hygiene reasons can never provide enough value to offset their cost.

    This is especially true in Orcas. Orcas is an in-place (not Side-by-Side) update to the .NET Framework. See the following blog describing more detail: http://blogs.msdn.com/somasegar/archive/2006/05/18/601354.aspx. Changing TimeZone APIs in Orcas would amount to breaking applications in a service pack. This is a big no-no.

  10. Diego Canepa says:

    Hi Krzysztof,
    I’ve a name suggestion, what about calling the new type “TimeRegion”

    Cheers

  11. I noticed a good debate going on the BCL blog (and now Krzysztof Cwalina’s blog) about the naming of

  12. Adam Jones says:

    I honestly don’t understand the problem with a breaking change…when you publish the next .net version … the dll version numbers are different, it shouldn’t break anything published unless you try and recompile…and if you are recompiling…its not such a big deal particularly for such a non impacting class.
    If you went and changed the interface on string … ok perhaps you have an argument…but otherwise ..what is the problem? Change happens, and from what i have seen of timezone2 .. the change is a good one. Don’t screw up a positive change with a confusing migration….and yes it is a migration because you are going to eventually get rid of system.timezone, so the code changes need to be done anyway…all you are doing by adding a 2 to the name is making it harder for programmers to find the right classes, which doesn’t help anyone.

    If you feel so compelled, please do include a framework API migration wizard in the next release, but keep the API name the same.

  13. Ever since Kathy Kam announced on her weblog that a new type named TimeZone2 will be introduced into

  14. Diego and Henry, I forwarded the suggestions to the BCL team. I actually talked to Kathy and she pointed me to the following blog which describes tradeoffs related to many of the names people suggested. See http://blogs.msdn.com/kathykam/archive/2006/10/06/Naming-Guideline-Discussion.aspx

    Adam, we are not changing version number in Orcas. This is basically the distinction between in-place and SxS. Also, even if we were doing a SxS release, it is a big deal for many people when they recompile and find a ton of errors. Migration tools cannot migrate everything. Also, there are various publisher policies which by default run applications on the new SxS release. Anyway, this whole compatibility discussion is probably a good topic for a blog post. I will ask around maybe somebody has already written on this, if not, I will try to write something.

  15. Micael Baerens says:

    When I read the design guideline, it says use numeric suffix "if the existing name of the API is the only name that makes sense".

    But "TimeZone" isn’t the only name that makes sense – there are many names that make sense…

    You could name it TimeZoneInfo as suggested in Kathys blog comments.

    You could name it after the reason you chose to create the new class – for instance if the main purpose was conversions it could be named ConvertibleTimeZone.

    So by your own admission you cannot apply this rule, and as such it must not be named TimeZone2… *yesss! SCORE!* 😉

  16. urig says:

    Dudes, TimeZone2 is an attrocious name for a class. We’ve been through this before with COM and it’s been a nightmare. Names need to be descriptive. Including a version number describes nothing about what the class does.

    If the new class cannot replace the old TimeZone then it should have a _significant_ name that explains why it’s different. Ex: DynamicTimeZone, WorldTimeZone etc.

    There’s nothing wrong with breaking the 3.0 codebase at this time. It’s still very far from release.

  17. Clayton says:

    I’m going to add my 2 cents in as well and say "Bad BCL team! Bad!"

    You can use all kinds of logic to say why TimeZone2 should be used. But when it comes right down to it, its still stupid. How does the quote go? "Logic is nothing more than a way to err with confidence."

  18. Micael and urig, I will pass your proposal to Kathy. She owns TimeZone naming.

  19. Judah says:

    Dear Lord! Please, please, please don’t do this to the BCL. Think of a different name, please! Your customers obviously cannot stand the appending 2 to the class name. Please don’t do this, please use a different name. I beg you.

  20. TAG says:

    > I don’t like fully qualifying core types when I program and usability studies we conducted have shown that I am not alone.  

    using System;

    using TimeZone = System.Globablization.TimeZone;

    Done !

  21. Anders Borum says:

    I have to agree with the comments on adding a "2" suffix is a bad decision. Especially the comments by Micael Baerens are interesting. There’s a number of other suggestions for names.

    The in-place installation is, however, an issue and this just adds to the arguments that .NET 3.0 should not be named .NET – rather .NET 2.1 (or 2.5) for what it is (.NET 2.0 with WinFX).

    Releasing the new version of the BCL as a side by side installation would allow you to make the breaking change now and use the "obsolete" features to inform developers about the new class.

    Already mentioned in other comments, the .NET 2.0 framework made several changes (some breaking). The changes to the Xslt namespace is a good example. The addition of compiled transformations forced a lot of people to update their code.

    Were the developers angry with this decision? no.

    Were the developers positive towards the investment Microsoft made in the BLCs? definately yes.

    It is extremely positive that Microsoft shows more transparency, allowing us – the developers – to comment on the decisions. We’re hoping it makes a difference (hint hint) 🙂

  22. TheMuuj says:

    I agree that a way to rename a class without breaking versioning would be nice.  I’ve thought about this before, and the only con to it would be possible confusion in the documentation.  If somebody looks up System.TimeZone in the help file and finds a listing for a completely different class that could be a problem, but not an unsolvable problem.

    So in case you’re considering a feature like this, count me in as a supporter.  I don’t see how we can get this in time to solve the TimeZone/TimeZone2 problem (it’ll have to be a major version update to support the new versioning capabilities).  But still, it’d be nice to think that someday after TimeZone has been obsoleted for a while we can rename TimeZone2 to TimeZone and TimeZone to _OBSOLETE_TimeZone without making any binary breaking changes.

    I’m not entirely sure how Reflection would work with type-renaming (I’m not sure what it does with type-forwarding), but if you have a way to see what version of a library an assembly was compiled against then I’m sure there’s a solution that would work.

  23. Jules says:

    Having built my own TimeZone class for a very large international law firm (because the System.TimeZone class is ridiculously short-sighted), I have done a lot of research about time zones and daylight saving time around the world and am very interested to see how this plays out.

    I’ve been reading a lot of comments in several blogs about the TimeZone2 proposal.  This is my take on the whole thing.

    1) TimeZone2 should not be based on the Vista "dynamic/historic timezone API", it should contain all the logic/data to compute this independent of the OS.  This is so much easier for the developer, less things to think about and consider.    

    2) TimeZone is a broken, almost useless class.  The ideal recourse is to replace it – it is already broken.  That said, if you are adamant about not replacing it, the new name should describe a class which clearly indicates that it is a replacement and therefore I am ok with the TimeZone2 name.

    3) Define interfaces so we have more flexibility… such as IDateTime and ITimeZone.

    4)  Also, I don’t like this: TimeZone2.FindSystemTimeZoneById(“Hawaiian Standard Time”);

    I would like all the time zones be listed, like the Color class lists many colors.  Easier discoverability, strongly typed (no misspelling bugs), less frustration and time wasted looking things up on msdn or wherever.

    5) May I suggest an instantiation of a TimeZone2 object have the methods:

    ToLocalTime(DateTime utcDateTime)

    ToUniversalTime(DateTime localDateTime)

    but still maintain the "ConvertTime" static method.

    I named my static method "ToOtherTimeZone", but I like "ConvertTime" as well.

    Last but not least.  I am very happy you guys are working on this TimeZone thing.  The framework certainly needs it!

    Cheers,

    Jules

  24. Jules,

    Thanks for the very detailed feedback. I have passed it to the team working on TimeZone2.

  25. agornik says:

    Hey Krzysztof.

    Did you thought about different solution?

    Maybe your should separate TimeZone class as a container and a manager of TimeZones (all that static methods and calculating stuff).

    I didn’t dig enought deep into the problem, but, perhaps, you could implement all time management features in a static (?) class named (for example) "Clock" (that would be really a good name) or "Time" and have a stupid TimeZone container (maybe even keep the old TimeZone class, or create a base class to improve compartability) . In that case, that would not be that important how this container would be called.

    The usage pattern will be something like this:

    Clock.GetTimeZone("name").IsDayLightSavingTime() .

    And the method could delegate the functionality back in the clock class via internal methods.

    I agree, from the point of OOP this is not the best thing to do, but from the point of usability, maybe it’s the thing to consider ?

    p.s: just finished reading your book (design guidelines) a couple of weeks ago, and want to say my "thanks". Great one.

  26. 為什麼推薦 ? Krzysztof Cwalina 是 .NET Framework API Design Team 的 Program Manager, 他也寫了 一本 Framework Design

  27. Stefan Wenig says:

    I think a lot of commenters here are simply ignoring an important part of what krzystof is saying: There is no new version number in .net 3.0 or 3.5 for assemblies that already existed in 2.0. Therefore, breaking changes are absolutely not an option, this is not a mere matter of preference.

    Consider what a breaking change would mean. Among other things it would bring library developers in an impossible situation. Once I update my libraries to the new version of the .NET assemblies, they are not going to work in older versions. Worse, strong naming won’t prevent this error because the version numbers did not change.

    The effects of such a decision would be simply unacceptable to any library developer (and cause problems for application developers too). We would end up having to query the installed framework version during installation (adding the requirement of an installation routine, which is unnatural for libraries), and this is still not robust, because the framework could get updated afterwards. This would be ridiculous.

    The other option would be to increase the version number. Thanks, but no thanks. Migrating build scripts and delivering seperate library versions for 1.1/2.0 was painful enough (not everybody is building their solutions solely via VS). We definately don’t want this only because of some obscure class most people have not even noticed before.

    Once we accept this, there are still a few things that I’m uncomfortable with. Krzystof, you seem to think that your solution would still be the best one even if the version numbers would increase to a new major number.

    1) You mention the possible use of publisher policies. This is technically correct, but this raises the following question: Why do we have to struggle with the unforgivingness of strong naming and still do not get to depend on it? I mean, doesn’t that give us the worst of both worlds – the missing robustness of dynamic programming AND the complicated build scenarios of statically typed languages? Arguably, in this case it could be better to drop the static checking altogether and depend solely on unit tests for robustness.

    2) If we ignore the publisher policy problem for a second, I’d strongly agree with the posters arguing for a breaking change in case of a major update.

    3) I would feel better about this decision if you would consider renamimg TimeZone2 back to TimeZone with the next major update of the respective assemblies. After all, once I have completely migrated to TimeZone2 and removed every reference to the old TimeZone, renaming TimeZone2 seems pretty painless (hit the obsolete message, search&replace in files).  And aliases are always a possibility. (Although I have to say that this would be much easier if i had a project/solution-wide way of defining aliases, having to put a "using" in every code file is a pain. Especially if I have to decorate them with #if’s. There are still moments when I miss those old #includes’s ;-))

    Stefan

  28. David Nelson says:

    Stefan,

    This is precisely why releasing an in-place "update" to the framework was a terrible idea in the first place. I am shocked that the BCL team didn’t see this problem or something just like it coming from miles down the road when that "solution" was first proposed.

    Krzysztof,

    "I would assert naming changes for hygiene reasons can never provide enough value to offset their cost."

    You could not be more wrong. Naming is the SINGLE GREATEST USABILITY ISSUE that developers deal with on a daily (no, line by line) basis. When I can’t remember which namespace KeyedCollection<> is in (who in their right mind came up with ObjectModel?), its because someone made a bad naming decision. That costs me time and breaks my rhythm as I have to fire up MSDN and go look it up. If you start making these kinds of decisions with every release, my productivity starts going down as I have to refer to documentation more and more often just to figure out the name of the class that I want to use. As previous comments have pointed out, Win32 suffered terribly from this. I use .NET for RAD (rapid application development); if your design decisions are slowing me down, .NET ceases to be useful to me.

    And as a side note, I completely agree with Stefan that project-level aliases are an absolute necessity. I hate when "moving forward" in languages (C++ to C#) causes me to go backward in functionality.

  29. Stefan Wenig says:

    David,

    I disagree. Keeping the changes in existing components on a service-pack level makes a lot of things easier for a lot of people. It simply means that I can cease support for the "old" 2.0 version and require customers to install the SP if I depend on any bug fixes, or I can just be agnostic of the SP-level of 2.0 components. 3.0 and 3.5 only affect me if I use those features. In fact, we put 3.0/3.5-dependent features in separate assemblies, so customers get a choice without us having to maintain seperate build processes as we did for 1.1 and 2.0. I wouldn’t want it any other way, really.

    TimeZone2 is just not a big enough issue to justify any of this. If stuff like that starts to be all over the BCL, I’d be with you though. Thats why I’d think it’d be worth the trouble of eliminating TimeZone and renaming TimeZone2 to TimeZone in the next major.

  30. Probably far too late for this.  But couldn’t most of this been handled with a "TimeZoneConverter", "TimeZoneManager" or using Extension methods?