HN user

cessor

321 karma
Posts7
Comments153
View on HN

It's easy to make Django react to "Accept-Language" headers, I love how the LocaleMiddleware supports it out of the box [1]. I implemented automatic language detection to show either German or English content and was surprised how many users hated it. We're located in Germany but a lot of people have their browser set to English for some reason, yet they still expect to see pages in German. Of course I had provided an explicit language switch but many users did not like the site guessing their language.

[1] https://docs.djangoproject.com/en/5.1/topics/i18n/translatio...

I agree 100%. Even if you don't need it, these lines don't hurt you, but most of the time, the additional model is put to good use. I usually add metadata, such as additional timestamps, flags, tags, custom user settings, consent or preferences.

I'd love to hear your criticism of django, issues you encountered and problems you have solved :)

My real criticism of Django is that, at long last, I think it's starting to look dated.

Actually, that is something I found really great about django. I found that django is reliable over a long period of time. The js/node/react/express/angular apps I had developed in the past (~2012+) can't be built these days without redoing major components. I never had these problems with django.

As for the responsiveness of the results, I found that a good understanding of django's performance helps. Using a good caching strategy (on models, views + nginx) yields results have almost instant reactions, even though there is the whole HTTP to HTML roundtrip. While they do have the noticable page-reload semantics, I found that these are way easier to build and sometimes yield very enjoyable experiences for my users. After all, I'm not building a YouTube so PictureInPicture and SPA-semantics aren't that important to my users.

Modern web apps are maintaining models in the client and staying current though a combination of ajax queries and websockets.

Julia Elman explains how to do this with Django + DRF and Backbone.js in the book "Lightweight Django" (O'Reilly). Is this a viable compromise?

Your're right, I felt the same way in that the article is clickbaity.

I found that django's naming scheme for migrations is good enough; here are some real ones that I pulled out of my current project:

    migrations/0006_add_modeltranslation.py
    migrations/0007_remove_temp_fields.py
    migrations/0008_remove_language_field.py
    migrations/0009_alter_page_slug.py
    migrations/0010_alter_menuitem_absolute_url.py
While they aren't semantically rich ('Why did you change absolute url on menu item?') I found that they are easy to distinguish and navigate.

The author names three 'harmful' defaults:

1. Relying on implicit SQL queries

2. Using the default User model

3. Using automatic migration names

4. Relying on automatic database table names

5. Using multiple tiny apps

6. Dumping apps in the root directory

I wouldn't call those harmful. Most are non-issues, while others are "unfortunate" at best.

1. I found that the ORM works quite well and it's easy to optimize later. Using `prefetch_related` or `select_related` in the ORM still feels weird (they're just joins), but I wouldn't call them harmful.

2. I found it unfortunate that the info on how to change the User model is hidden so deep in the Documentation. I don't like to RTFM, but with Django it sure is worth it; same goes for 1.

3. I occasionally do it, but most of the time, I don't and never had an issue.

4. It do rely on that and found django's naming scheme to be a non-issue. I guess this might be more important if you need to change your database out of band (out of django band, that is)

5. This point is unsubstantiated. Apps aren't mere packages but also define a scope for the admin tool (which I use a lot and think is an integral part of Django) so that it will group models and allow individual access (i.e. apps also provide scope for your permissions). I use tons of apps but I wouldn't call it a harmful default.

6. Yeah, dynamic languages might produce naming collisions. Still beats imports like `org.example.publib.boulder.yourmoms.maidenname.models.impl.Account`

Does your employer pay for your technical skillset and your labor, or do they pay you for sacrificing your mental health? I don't think self-care is unprofessional, and deciding to leave is perfectly fine.

Professionalism means being able to produce convergent results given varying resources, but when the spec is unclear, your employer doesn't provide appropriate resources for you to perform or if your mental health is in danger, I think it's perfectly valid to back off. "Professional" does not mean "sorcerer". A professional photographer can't take a good picture in complete darkness, and even a good architect can't create a distributed system when the environment won't support it. It argue it's professional to know your own limits and reject a job offer.

It's difficult to make the decision to leave, especially when you have been jobhunting for a while and I can't say anything substantial on how anyone should make such a decision, but the other day I found a meme saying (something along those lines):

In a job, you should either earn or learn; either is good, both is ideal, if it's neither: leave. If the job pays ok money, but costs your sanity, I'd argue you should leave.

This is judging a fish on it's ability to fly. In other words, it is judging an object oriented program on its ability to resemble top-to-bottom sequential execution style programming, while ignoring that the language is commited to a different paradigm.

Some of the criticism is really superficial:

    function createEmployee(firstName, lastName) {
        return {
           firstName: firstName,
           lastName: lastName,
       };
    }

OO-Programs have these weird factory functions called constructors that do exactly that. To me, it makes sense that such functions should share their living space with the class that they are creating. Where else would you like to put these functions? "Utils"?

As for classes, namespaces and objects: as others have pointed out, this is an implementation detail. In Python, for example, classes are objects. Besides, classes and objects are functions (and the other way around) so it doesn't matter all that much in practice.

What buggs me more is code like this:

    class SayHello {
        public static void toPerson(String name) {
            System.out.println("Hello " + name);
        }
    }

Sure, comparing it to an empty Python file which just says "print" this looks like an aweful lot of code. However if the paradim is poorly understood, it will seem to be working against you:
    class Name {
        public Name (string name) {
            this.name = name;
        }

        public static void greet() {
            System.out.println("Hello " + this.name);
        }
    }

The programming is different in that you create a declarative network of objects that then ... act. At this level, there is hardly any difference, except that the name has been introduced as a proper concept. Many people will argue that such value objects are expensive or inefficient (which they are) but if that cost hits you hard, you should be asking yourself why you picked a high level language to begin with.

Yes, in such a paradigm a place is needed where the entry point lives, but I found it fundamentally useful. Whatever you call the class it: - Can read and encapsulate access to env variables - It can accept commandline arguments (as main(argv[]) indicates) - It's the place to setup and configure your (object)- dependency tree (what IoC/Dependency Injection-Containers are doing) - Or it can be the ultimate exception handler for messages deeper in your program, e.g. where you would start your main message pump / game loop, etc.

The rest is ergonomics.

I don't use a debugger. When I have a problem that I need to narrow down I state my hypotyhesis of what is going on in some kind of unit test and narrow it down that way.

I am currently working on a Python/Django project of around 45k lines of code, 28k of which are tests. For me, this approach works quite well. It is not that I am against debugging or that I don't value debugging as a tool, but I just never feel the need to use one.

The tests also have a nice side-effect: because I do TDD a lot, I mostly end up with a lot of small understandable units, so I am effectively preventing code with properties that need me to follow each statement one by one.

I hardly ever feel the need to interrupt the running process to inspect the current variables. In many cases, this would be futile anyway, because I started writing a lot of generators.

When using it like this, the interpreter opens a new namespace where the variables are bound. The x is declared in the outer scope, so it can't find it. However, you can ask python to keep searching vor the name `x` in the closest outer scope, that is what the `nonlocal` statement is for:

    def f():
        x = 5
        class Blub:
            def incx(self):
                nonlocal x
                x += 1

            def getx(self):
                return x

        return Blub()

    j = f()
    j.incx()
    print(j.getx())
This will print 6, as expected.

True. But given that the difference in implementation appears to be arbitrary, the real differene between oop and fp must be evaluated in the domain of empirical research regarding their ergonomics. If one matches your natural thoughtpatterns more closely (be that abstract or concrete modelling) or educative efforts (it might be that one is easier to understand), or ease of implementation, that would be an argument for or against it.

Although Java might be conceptually weak, empirically speaking it's a big winner, because developers appear to value ease of education, connectedness with others, availability of jobs and job security more than feeling stupid for not understanding lambda calculus. (that doesn't render formalist approaches invalid)

Checking the validity of the data is only necessary once.

Exactly, which is why a class is the perfect singular location to place it. The object itself is just a pointer, the method doesn't get copied around, so object appear to be the perfect method to localize code.

unexpected exceptions

True, exceptions introduce a communicative issue, but so does returning in-band values.

For example, what should the result of

    open('file.txt').read()
be, when the current user does not have permissions to read file.txt?

Different approaches exist:

    status, content = read('file.txt')
    content = read('file.txt', &status)
    status = read(&content)
I wouldn't argue against any of them, although I have my preferences of course. I like the exception model here, but you are right, rare exceptions, communicated poorly can be surprising and painful.

Comprehensibility is in no way related to using objects.

On itself this statement is false, ... (hear me out)

It all depends on the quality of the naming

... But this makes me understand what you're trying to say, and I 100% agree with it. In fact, I conducted some experimental research on identifier naming: https://link.springer.com/article/10.1007%2Fs10664-018-9621-... (Sci-hub or I can provide a preprint).

You are right in that objects and their use don't automagically turn a codebase in a field of readily availabe knowledge. Many mechanisms applied in OOP languages really work AGAINST comprehension (for example, buried exceptions originating from deep within an object graph). But still, objects are tightly coupled to comprehension, even historically speaking. They were first used to make it possible to model physical simulations without requiring users to know much about computer architectures (Simula 67). Objects are meant to "model", that is, symbolically represent concepts, entities or physical things in such a way that they might show agentic behavior. This is fundamentally different from having stupid data, smart functions but actually a completley different means of "Erkenntnis" (translates to "insight", but is more accurately conceived as "Epistemology"). The relationship between objects and readability / comprehension is complex, but I wouldn't call them "in no way related" (OOP can break comprehension, but it was invented to improve it. Irony.)

Also I would, again, like to second your words: Identifier naming might be the most imporant aspect of readability and comprehensibility.

I will admit that I don't know what "linear types" or "affine types" are. Your answer makes me feel like I am trying to convey an idea for which you have the perfect formalism / meta vocabulary. It might be that objects, or at least the way that I know how to use them in my favorite languges, are just a (potentially limited) implementation of said formalism.

I feel that objects offer a flexibility benefit though (which is why they often elude formal approaches) for the cost of purity.

What happens to your OpenConnection object then?

It raises an exception. This interrupts the normal flow of things and asks you to deal with the problem asap. If necessary, the handling code then could try to reconnect or abort. If desired, it could return a closed connection to convey that state-change, so that calling code is made aware that it needs to reconnect first and can't just reused the now closed connection. You could revert it to a normal connection (a "Has never ever been opened to begin with"-Connection). Depends on whether your driver/adapter/underlying connection thing cares about a difference in initial connects or reconnecting. If the handling code can't deal with it, it can bubble the exception up one level.

Swapping a `Connection` for an `OpenConnection` isn't heretic by the way, such structures are described by the state pattern. Objects model state, Exceptions model events (state transitions) but the later isn't explicitly described in the original Gang of Four book that way. I just found that exceptions are very usefull for this, given that you react to them in only a limited scope.

Be aware that this idea is culturally dependent. In Java, Exceptions are often discouraged from being used in such a way (exceptions should never be used for control flow and only convey error cases), in Python it's normal to communicate state transition that way, e.g. for-loops watch for StopIteration exceptions.

Members in Python are never private. Variables that are supposed to be used internally only are marked with and underscore, but that just conveys intent and isn't enforced by the interpreter/runtime. But you can emulate private data like this:

    >>> def a(u, v):
    ...     def b():
    ...         return u + v
    ...     return b
    ...
    >>> b = a(1,2)
    >>> b()
    3
Like this, there is no way to access u & v from b.

The term information hiding used in this thread is very confusing (to me, at least).

I believe David Parnas introduced it in 1971 to mean that a program's design was sliced along shared units of concerns (things that vary together) rather than "steps in a flowchart".

https://prl.ccs.neu.edu/img/p-tr-1971.pdf

I believe what you are trying to convey is called "data abstraction", as for example used by Reynolds, 1975;

mentioned here: https://www.cs.utexas.edu/~wcook/papers/OOPvsADT/CookOOPvsAD...

explained here: https://link.springer.com/chapter/10.1007%2F978-1-4612-6315-...

This circles back to my original argument. Here:

    def adder(x):
        def add(y):
            return x + y
        return add

    add_five = adder(5)
    add_five(10)
How is that different from:
    class adder:
        def __init__(self, x):
            self._x = x
    
        def __call__(self, y):
            return self._x + y
     
    add_five = adder(5)
    add_five(10)
Did you have something like that in mind? Do I understand your idea correctly?

They both allow you to achive the same thing. The syntax doesn't matter, they both produce the same result (in Python, that is, which is a bit unfair, because there, functions are objects to start with -- but even if this was not the case, then the object would require namespacing which is negligible). It depends on whether you want to `think` with a closure or not.

I propose that differences in OOP and FP on this level are irrelevant, but that the magic lies in the naming chosen here. An "adder" is something that has continuity and identity, and the calculation is performed later. I don't care about how it is constructed. There is nothing wrong with just calling add(1, 2) -> 3, but objects give you this deferred stuff for free (sorry, not free, for the price of some memory).

It's not so much about "I need that later", but to start thinking about a program differently to begin with. If you think about the things that "are" rather than what should happen in what order, you get the chance to rearrange everything and do some optimizations that an eager processing might prevent you to do. Most of the OOP power comes from uniformity, which many systems break unfortunatelly. When you work with a system in which everything is an object, you can start to relax a lot, although everythin is conceptually slower. Most software doesn't even have to be that fast. If it has to be, feel free not to use Ruby.

The tradeoff is most often performance versus comprehensibility. I'd argue in favor for the latter. Of course, ergonomics and ease of use are hard to measure (but not impossible, although empirical studies are quite difficult to do and expensive), but the tradeoff is similar for all higher level languages. Consider the overhead for a class `Year`:

    `Year(2004)` --> valid
    `Year(200192)` --> Exception 
In its constructor validates integers.

Insane! Expensive! you might say. All it does is encapsulate some integer! But I'd take that little overhead over the scattered insecurity of my colleagues every day, who in every calling method will do the same "if then that else"-check for the year range over and over again, when they are handed an int and need to find out what's in it. The class provides locality for my concern that I only ever want to deal with valid years.

When, in your system you find a year-typed object somewhere, it is guaranteed that this is valid. This creates peace of mind, which is way more expensive than RAM.

If you limit the scope of usage, the old wallet should be garbage collected as soon as you have the new one. If you don't want to rely on that, or the point in time when gc happens is important or you're dealing with sensitive data, then the environment should allow you to perform appropriate cleanup actions and for you to utilize a different means to control and protect the data.

Funny you should mention that. For my (disclaimer:unfinished) PhD I did a literature survey including 17.000 publications from 1960 to 2018 in order to find a definition of object-oriented programming that would allow us to improve on our fMRI research designs.

It turns out: some 95% of all official ACM/IEEE publications mentioning OOP, never explain what they mean. Some even have oop in their title but then just explain some crude java library, never explaining why oop is a good fit for their problem to begin with.

Finding a good definition is REALLY hard, but there appear to be two departments, outlined perfectly in the book "Object thinking" by David West:

1. Formalist (G. Booch et al.): Object-Oriented Programming is Programming with Objects, Classes and Inheritance

2. Hermeneutic (A. Kay): Objects should be inclusive, are a recursive projection of a cell-like organism metaphor.

Many try to mathematize the idea, use it for analysis and to validate programs, derive some sort of object calculus and care about the mechanisms. Others (the hermeneutic camp) where more about how objects do or do not support your conception of reality so that you can model and simulate it using rocks that we tricked into exibiting calculative thinking.

I found that one ubiquitous definition (Kay's infamous "messaging, local retention and protection and hiding of state-process") isn't helping at all, but people have been struggling to find a clear and consicse definition, which is why Booch and Kay are always put forward.

This is the only thing I ever got out of it:

http://pi.informatik.uni-siegen.de/gi/stt/38_2/01_Fachgruppe...

I like it. It gives you (f)actual private fields in python, as title and name can't be accessed via the instance variable of f.

That also works in JavaScript:

    function Fraction(a, b) {
        this.value = function () {
            return a / b; 
        }
    }

    const half = new Fraction(1, 2);
It feels weird to me, that classes were eventually introduced in JS.

inheritance is bad

That depends. Inheritance makes it easy to break encapsulation (which is bad -- agreed). It can be hard to model "Is-A"-Relationships properly, but I wouldn't call it inherently bad. A circle isn't an ellipsis; but a chair is furniture. The quality of code stems from your quality of thought.

this means tight coupling between them, rather often unwanted.

That depends on your design. Coupling is the whole point, you do objects because you want to couple. Fraction.reduce, Fraction.add, Fraction.subtract, Fraction.multiply. Why not have them as as a cohesive unit, all these functions must understand the details of fraction anyway. Why not couple them?

Not having mutable state is the point.

I agree. But objects never force you to publish their state; that's bad education. Getters and setters should be avoided. The Fraction above can be made immutable easily, Fraction(1, 2).add(Fraction(1,4)) --> <Fraction: 3/4>, leaving the old ones intact.

But then, it depends on how you communicate state.

I believe `Connection.Open()` should return an Object of Type `OpenConnection`. I believe that state changes should be communicated by changes of identity (either a new instance or a new Type), but ideas like this are most often answered by waving torches and pitchforks in front of my house at night (figuratively).