Hi Pascal,

I know this is a very late reply, but there have been some things going round my head that I thought I should get down.

*First,* some positive changes I think we can make from your suggestions:

1. I think in our release notes, for breaking changes that might need some justification, we should link to the tickets and/or django-devs discussions behind them. Adding full reasoning in the notes themselves would likely bloat them far too much, but a link could be helpful for anything which is not obviously necessary.

2. We should change the wording in our API stability docs <https://docs.djangoproject.com/en/stable/misc/api-stability/>, which does indeed initially sound like a promise never to break working code that follows docs:

   Django promises API stability and forwards-compatibility since
   version 1.0.

We do have this line later on:

   If, for some reason, an API declared stable must be removed or
   replaced, it will be declared deprecated but will remain in the API
   for at least two feature releases. Warnings will be issued when the
   deprecated method is called.

But we do make changes at a rate faster than that document would make you think.

I suggest that as a contrast to the existing text on that page, we also point out that we are continually improving Django, and have a "(eventually) one way to do it" policy regarding how we plan APIs, which means we will remove old things, while always providing better alternatives. I'm happy to write a patch for this.

*Second,* there were things I wanted to say that might be useful to you (Pascal) in understanding why you are not likely to get very far with your current approach.

Your exaggerated tone is not likely to help you. You talk about Django's compatibility breakages as "/ruining the day of thousands of anonymous plugin/website maintainers all over the world/". When we make these kind of changes, we always do it with very clear and detailed upgrade notes, and almost always with several releases emitting deprecation warnings, as per our policy. And no-one is forced to upgrade immediately. Those who value stability above other things can use LTS, which gives them a *3 year release cadence,* not 8 months, which is far from excessively fast compared to other projects.

My involvement in Django these days is primarily not as a core contributor, but as a user and library maintainer, and I've also contributed many patches to different 3rd party Django projects to fix them for Django upgrades. The impression that you give of Django being a continuously shifting sand is very far from my experience. The core features and structure of Django have remained remarkably stable over its history. A few import changes here and there are very easy to fix (and, more importantly, noisy and easy to diagnose), and other changes are usually straightforward. Normally upgrades to libraries I maintain to support the latest Django version require very little work, sometimes zero, and often I'm able to support 4 or 5 Django versions without a problem. Using something like django-compat-patcher, or some of the other similar libraries, would add more complexity than the few lines of code I need otherwise.

Even larger, more complex Django projects like Mezzanine, which use quite a few hacks and internals, are often able to span across two LTS versions.

With all this in mind, it's difficult to see how we are making so many people's lives miserable.

Instead of debating in abstract, or answering your points one by one, I thought a few illustrations might help you see the benefits of Django's approach. The first is the longest, it might be of historical interest to others.


   1. A 14 year old Django project

I started work on my first Django project in September 2005 <https://lukeplant.me.uk/blog/posts/a-django-website-that-took-a-lot-more-than-20-minutes/>, and it went live in March 2006. It has been running continuously since then - perhaps by now it can claim to be one of the oldest continuously running Django sites? I'd be interested to know if anyone can beat it...

Here's a snippet from one of my models.py nearly 14 years ago:


   class Message(meta.Model):
        fromUser = meta.ForeignKey(User,
            verbose_name="from user",
            related_name="messageSent"
        )
        toUser = meta.ForeignKey(User,
            verbose_name="to user",
            related_name="messageReceived")
        time = meta.DateTimeField("At")
        text = meta.TextField("Message")
        box = meta.PositiveSmallIntegerField("Message box",
            choices = MESSAGE_BOXES)
    def __repr__(self):
            #return str(self.id)
            return "[" + str(self.id) + "] to " + repr(self.get_toUser())  + " from 
" + repr(self.get_fromUser())
    class META:
            admin = meta.Admin(
                list_display = ('toUser', 'fromUser', 'time')
            )
            ordering = ('-time',)


Wow, just look at that! ORM stuff under a `meta` namespace. Admin options stored under the model META inner class. These funky `get_XXX()` accessors for related fields. (Not to mention my naming conventions and code formatting back then...) While I have memories of some of the big changes in syntax that happened pre 1.0, I had no memory that we use to do most of these things. Why? Because this project no longer does it this way. My brain thankfully reclaimed the space taken up by these old ways of doing things. If we developed Django according to the backwards compatibility philosophy you are suggesting, that would not have happened. *Instead, my code would be just as bad/quirky today as was did back then*, for two reasons:

1. Without being forced to upgrade, I probably wouldn't have done.

2. If Django devs had needed to keep supporting old ways of doing things for long periods of time, two things could have happened:

 * they would have been very conservative about adding new things,
   because it adds complexity to Django itself, and because of the
   principle of "One Way To Do It". So the new improved ways often
   wouldn't even exist.

 * or, they would have just piled on the new things while leaving the
   old. Quite quickly you get to a point where the quantity of cruft,
   and the need to support all the different legacy ways, means that
   adding new things becomes very difficult, and development would have
   stagnated and quality dropped. To use your illustration, you can't
   build a castle on a weak foundation.

When I work on this old project I find:

1. *It is virtually indistinguishable from brand new Django code.*

2. *The code quality has improved over time, and the project has never been in better shape than now.
*

I have none of that "Oh no I've got to fix that old project, how did we use to do this stuff again?" feeling. This project is a breeze to work with, and feels completely modern. If I needed to pass it on to someone else, I would be less worried now about doing so than I ever have been. How many other 14 year old projects can you say such things about?

I could multiply examples. Take URLs. Here's the old code:

   from django.conf.urls.defaults import *

   urlpatterns = patterns('cciw.apps.cciw.views',
        (r'^thisyear/$', 'camps.thisyear'),
        (r'^camps/$', 'camps.index'),
        (r'^camps/(?P<year>\d{4})/?$', 'camps.index'),
        (r'^camps/(?P<year>\d{4})/(?P<number>\d+)/?$', 'camps.detail'),
        (r'^sites/$', 'sites.index'),
        (r'^sites/(?P<name>.*)/$', 'sites.detail'),
        (r'', 'htmlchunk.find')
   )

Versus the current code:

   from django.urls import path

   from cciw.cciwmain.views import camps as camp_views
   from cciw.cciwmain.views import sites as sites_views

   urlpatterns = [
        # Camps
        path('thisyear/', camp_views.thisyear, name="cciw-cciwmain-thisyear"),
        path('camps/', camp_views.index, name="cciw-cciwmain-camps_index"),
        path('camps/<yyyy:year>/', camp_views.index, 
name="cciw-cciwmain-camps_year_index"),
        path('camps/<yyyy:year>/<slug:slug>/', camp_views.detail, 
name="cciw-cciwmain-camps_detail"),

        # Sites
        path('sites/', sites_views.index, name="cciw-cciwmain-sites_index"),
        path('sites/<slug:slug>/', sites_views.detail, 
name="cciw-cciwmain-sites_detail"),

        # ...
   ]

There are many improvements, for example:

 * Star imports have been removed. There are lots of good reasons why
   every Python code linter complains about these. Or would you rather
   still have them?
 * In my editor I can put my cursor on a view function like `index`, do
   "jump to definition" and it does the right thing, because it is
   normal Python. Do you think dotted strings were better?
 * My list of URLs is just a list, and can be manipulated with normal
   list methods and functions (which I often need to do). Do you think
   wrapping everything in `patterns()` would be an improvement? For the
   sake of an extensibility we might just use someday (though we didn't
   in well over a decade)?
 * The addition of `path` is a great help, and has really cleaned up
   URL handling a ton, and even more in other areas of my project. I
   wasn't forced to make this change, but it's a great improvement.
   *However, this is the kind of big addition that the Django
   developers only considered and were able to do because the Django
   codebase was not a pile of backwards compatibility hacks and cruft
   already. *And yes, doing so meant we lost some /undocumented/
   classes which were implementation details, which you also seem to be
   complaining about.

Everything is simpler and more Pythonic - while at the same time the fundamental structure and abstractions are extremely similar, and every migration has been straightforward. It is unfortunate that we didn't have a crystal ball in order to optimize these migrations a bit more, or indeed get it right from the start. But that's life, we don't have crystal balls.

All of the changes with URLs have had thought through reasons. Often the changes fix issues that newbies consistently trip over (based on feedback from people who do a lot training of new Django users) and security issues we've had.


   2. Wordpress

You mentioned PHP and Wordpress, and I would agree that their approach is quite different. But it also comes with huge costs. Thankfully I already wrote a blog post that covers pretty much everything I want to say, with quite a few comparisons to Django:

https://lukeplant.me.uk/blog/posts/wordpress-4.7.2-post-mortem/

A zero day vulnerability in which your web site gets defaced and possibly fully hacked is what I think of when I hear the phrase "my day was ruined", and that is the kind of vulnerability we've so far been able to avoid in Django.

Critical security vulnerabilities like the above are also just the tip of the iceberg - the same issues that cause them also cause all kind of bugginess which hurt you in much more every-day ways, but these costs add up.


   3. AngularJS

I'm surprised you let AngularJS off the hook so lightly. One of my clients has a project with 25,000 lines of AngularJS code. Having looked at the detailed upgrade instructions, in the context of our application the first step essentially amounts to "first re-write your entire app" (to use components everywhere, which we are not). There are entire books <https://www.upgradingangularjs.com/> devoted to this upgrade, it is a massive undertaking that requires a deep understanding of both AngularJS and Angular. The team is considering ditching everything and starting with a new framework. This is the kind of situation that leaves businesses in very bad situations, having to choose between *either *being able to deliver new features for the next six months to a year *or* rewriting and paying off the technical debt.

For my past and current clients, and all my side projects, I have never faced anything remotely closely to that choice with Django. Sure, we've had to schedule in some time for upgrades, especially if we've had a lot of dependencies, but doing the upgrades, and fixing most of the deprecation warnings too, for small to medium sized projects, usually has been work on the scale of hours, sometimes days — maybe a week of development work if we were ahead of the curve in terms of upgrading and had to submit patches to lots of 3rd party dependencies. But never months — never anything close to "senior management needs to get involved with this decision".


*To sum up,* Django has so far avoided the huge upgrade costs of something like AngularJS. We've never reached the stage where we've thought "It's too awful, we'll have to start again", and never had to write that blog post - exactly the kind of news which might indeed ruin your day as a web developer or project manager. This might have been luck at choosing the right abstractions while AngularJS had some bad luck, but whatever the explanation, it's a great thing. At the same time we've avoided the horrors of a codebase like Wordpress which has just kept going with layer upon layer of cruft. Old Django projects (that have been maintained), and the Django code base itself, remain a pleasure to work with. That's also why we're able to attract excellent developers, like our current Django Fellows, so that Django continues to improve.

And we're not done yet - Django appears to have quite a bit of life left it in still! I'm convinced that all of this has been due to it's approach to improvement, gradual backwards compatibility breakages, and not tolerating previous bad design <https://lukeplant.me.uk/blog/posts/why-escape-on-input-is-a-bad-idea/#a-lesson-for-pythonistas>, rather than despite it.

Hope that helps,

Luke


--
You received this message because you are subscribed to the Google Groups "Django 
developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/b907b24d-29d9-8994-5d1a-154ca62e4a7b%40cantab.net.

Reply via email to