Django 中糟糕的部分
前段时间中,我被委托开发了一个使用Django后端的Web应用。在一段时间的开发之后,我想说一下我所遇到的Django 中的一些糟糕设计。
凭空出现的 get_FOO_display()
在 Django 的 Model 中创建一个 Choices 字段 FOO,Django 会自动为其创建一个对应的 get_FOO_display() 函数。开发者可以通过这个函数来获取该字段对应的“人类可读值”。
这是一个非常奇怪的设计,get_FOO_display() 明明依赖于 FOO 字段的存在,却如同“凭空出现”一般。它们互相依赖,却没有任何明确的静态关系将两者联系起来。
from django.db import models
class Person(models.Model):
SHIRT_SIZES = [
("S", "Small"),
("M", "Medium"),
("L", "Large"),
]
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
试想一下这会导致什么结果。如果你决定修改 SHIRT_SIZES 字段命名或者移除该字段,因为 Python 是一门动态语言,你不会收到提示说你使用了一个无效的函数。项目在代码运行在 get_shirt_size_display() 之前一切正常,然后崩溃发生。
字符串绑定的 related_name
在 Django 的 Model 中定义一个外键字段,为了能从引用的外键中反向找到该对象,你需要在字段中添加 related_name 属性。但该属性只能被设置为一个字符串类型,这也就意味着这个绑定关系不会受到任何约束。
class Asset(models.Model):
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='assets',
on_delete=models.CASCADE
)
在此之后,如果你对代码进行了重构,修改了 related_name 对应的字符串,你同样不会收到任何提醒,但错误代码已经产生。如果你的工程中还有像下面代码这样未修改的访问,一个崩溃就会产生。
# You can access Asset object from owner like below:
user = self.context['request'].user
user_assets = user.assets
同时,如果上面这样的代码在你的工程中频繁出现,这也会加重你的重构成本,导致代码更加难以维护。
字段查询
Django 的字段查询语法非常易用,但也具有风险。例如,你可以使用下面的语法来查询pub_date字段在"2006-01-01"之前的对象。
>>> Entry.objects.filter(pub_date__lte="2006-01-01")
Django 通过 Python 的 Keyword Arguments 功能实现了这样的功能,这种动态的实现导致pub_date__let这样的属性不举报任何绑定约束的。不具有绑定约束的代码在越来越庞大的项目中,对应的字段的重构成本将变得无比高昂。
动态语言难以提供太多的绑定约束
上述这样的问题或许不应该说是 Django 糟糕的地方,而是使用动态语言 Python 所导致的结果。动态语言无法为这些绑定关系提供类似编译期的约束,这不仅导致了潜在的错误,也增加了重构的成本。
动态语言不需要对代码进行编译,这为代码的开发提供极大的便利性,在较大型的项目上更是节省了巨大的编译时间成本。但同时这也意味着它不能利用编译期带来的静态绑定的约束,错误的语句在被运行到之前也无法得知。
另一个极端是像 Rust 这样的静态语言,在编译期设置了类型、内存等严格的检查,带来了内存安全、更快的运行速度、无需垃圾回收。但对应的,这也导致了 Rust 的编译时间也比大多数的静态语言更长。