@Lenciel

Django其实不是MVC

Vhost threshold

很多时候Django都被称为是一个MVC框架 — Model-View-Controller。这样说的人要么就是已经熟悉过其他的MVC框架,所以看到Django有自己的Template系统和views.py来放各种逻辑代码,就想当然的认为Django也是;要么就是其实没有真正实践过Django,从各种其他的错误文档里面看到或者是听说的。

MVC框架,是针对状态的。为了明白这个,我们假设你是在编辑一个图片:

  • 你得在内存里面保存这张图片 (Model)
  • 你得在屏幕上显示这张图片 (View)
  • 你得有办法让用户改变图片 (Controller)
  • 当用户改变图片后,你得更新显示:Controller通知Model更新状态,然后Model通知View刷新显示(最好是通过某种pub/sub机制,让View和Model之间是没有耦合的)

MVC框架主要是管理状态,让MVC三者是同步的:这三部分同时在内存(可能跑在不同的线程甚至进程)里,有各自的状态,相互之间进行交互,让变动同步到各方。

Django的Model-View-Template有很大的不同。

首先是没有状态。大多数的HTTP GET请求,拿到的数据库里面的数据,都被当成是immutable的不可重入的输入,而没有状态。而在一般的Web应用开发中,HTTP上有状态的交互可以通过:

  1. 修改保存在服务器端数据库里的数据
  2. 修改保存在客户端的数据(比如cookies)

共同来完成。因此一次状态的变化并不是在一个page的view里面保持的:状态一半放在当前的page和cookies里面,一半放在session数据库里面。

但是处理HTTP请求的时候,Django的MVT是完全无状态的。这里首先要说明的是,views.py这个名字本身是有一点儿误导的,因为给人的感觉是它只做"读"操作而不去"写"数据库(也就是说只是处理GET请求而不是POST请求)。但实际上GET/POST请求都会被放在views.py里面处理,所以更好的名字其实应该是handler.py:大多数Django的REST框架都是这样命名的。

处理GET请求的时候,如前面分析,本身就没有状态,而只是对输入的请求和服务器返回的数据进行展示。其次,当涉及数据修改的POST请求时,Django的处理其实是非常类似于老式的Web应用的。

所谓的老式的Web应用是指,过去的网站上当后台的数据发生变化的时候,其实是需要用户在前端自己点击刷新按钮来刷新的(最典型的刷新按钮就是浏览器里面那个刷新按钮)。这个动作背后发生的事情其实是:

  1. 除开标识当前是哪个用户在浏览哪部分数据的信息(当前的url,用户的identity等等cookies里面的数据),把浏览器里面其他的状态都丢弃
  2. 发起一个全新的请求,获取所有的数据,再次重建页面

说Django和老派的Web应用类似,是指一旦数据变更(比如一次SQL的INSERT或者是UPDATE),你需要返回一个redirect再做一次GET:"有数据的状态变化了,让我们重头再来一次"。

这也是为什么Django的ORM里面是没有一个"identity mapper"的Model处理状态变化的办法就是完全的无视它:当你觉得数据改变了时,直接重新获取一次数据重建页面。

这和大多数经典的MVC框架(比如AngularJS)是和这完全相反的套路:在设计上做了很多事情来避免"从头再来",而是通过建立MVC之间的消息机制,来通知各方的状态变化,做到同步。

MVC还有一部分是关于如何分隔代码。如果你把MVC当成:"把存储数据,显示数据和处理数据的代码分离",那Django的设计的确是符合这个模式的。

但是实际上这是一个粒度非常粗的描述,因此就把Django说成是MVC的其实会带来很多误会。

比如,Django是基于HTTP的,所以理解它的MVT,最好的办法就是实践它:看它的view里面是如何处理一个HTTP的请求并返回一个HTTP的response。如果你脑子里面有其他的不是基于HTTP这层次的MVC框架,用来类比学习Django,你大概会哭…

其次,Django的框架,它的app里面文件的组织和使用,和很多别的MVC框架也是不同的。

最近有个特别火的日志是Hynek Schlawack的Know Your Models。它是基于经典的MVC框架来假设,实际上Django并不是适用于这套假设的。

比如他觉得应该有pure的models,从而把M、V、C分离开做到可以独立进行处理。

但其实Django里面很多app都仅仅是数据库的简单wrapper。这种情况下其实没必要有pure的M,然后再加上一堆V和C。其实这是Django的美好之处:以admin这个app为例,它的设计初衷就是要在数据库上面封装一个足够简单的编辑层,以致于95%的代码都是可以自动生成的。

Model通过API暴露出来给View用,当然也是正确的思路。但我自己写代码的时候,就很喜欢把所有的直接调用.filter()的代码都放到models.py里面,这样一来models.py就是独立可测的。

并且,如果你写了一个pure的Model,而把逻辑代码从Model里面抽取出来放到别的地方去,那你在admin和其他ModelForms里面就没法重用了。

总的来说,在Django里面,model在创建的时候,就是有业务逻辑贯穿在里面的。如果你每个customer只能有一个email,那么你的model就得包含这个限制。如果你要改变这个规则,那么就不仅仅是MVT的某一方要改,而是从上到下都得修改。

甚至我个人认为MVC里面说的"逻辑和数据分离"这种思路本来就挺奇怪的。除非你把数据存储当成key-value这样的东西,那么你怎么可能在一个不是为了某个业务逻辑设计的数据库上开发出一个应用呢?

数据就是数据,是gloabl data而不是gloabal state。在整个HTTP请求被处理的过程中,它被认为是没有变化的:如果有,就应该再发一条请求来取最新的数据再去重画。

当我们开发Django的应用时,为了满足实际上的业务逻辑的需要,数据库的schema一般一直在变。这样Django的model就可以作为API的一个良好的基石,把往上走的事情做得尽量简单。

这涉及到软件开发里最基本的一个设计要点:你把数据库仅仅当成应用里面的持久化层,还是当成应用的一部分,甚至是最重要的一部分。

我其实一直偏向于后者:喂,把像Postgres这样RDMS当成一个持久层未免也太不尊重了吧!所以在使用Django设计app的时候,不但要思考"model layer",还要综合考虑其他数据库可以做的事情:比如contraint checking, transactions, triggers等等。同样,在测试的时候也不仅仅是测试那些字段和model之间的关系,而是要考虑对业务逻辑的测试。

Agile and Scrum, The Love Story

Vhost threshold

本次吐槽献给Scrum Master们。毕竟了解了软件社区其实对Agile和Scrum的情绪已经有些像走到结尾的爱情故事,也许可以让大家在工作中不要把自己和大家SM得太惨。加上我们进新公司之后也在推行敏捷流程,不如整理一下本座对这套东西好鬼复杂的情绪…

Agile,中文翻译为“敏捷”,是在90年代逐渐引起广泛关注的一系列新型软件开发方法的总称。其中“敏捷”的语义主要是指应对快速变化的需求。

敏捷思想发展到顶峰的标志是Agile Manifesto的正式定稿。当时大概谁也没有想到,2001年二月这群敏捷方法发起者和实践者在美国犹他州雪鸟滑雪圣地的一次聚会后的产物,能在软件工程和方法论范畴独领风骚这么多年。

但敏捷再好,它毕竟是人写的不是神写的,也会随着技术的不断革新慢慢变得过时。类似的,Scrum这种用于实践敏捷的开发流程虽然也大红大紫了这么多年,但它也暴露出了不少缺陷。

瀑布文档过多,敏捷文档过少

世界变平了之后,大多数的公司团队是分散在多处的。即便是TestBird这样没有超过100人的公司,办公地点也分布在多个国家和地区。而在编写Agile指南的2001年,本座还细皮嫩肉,Subversion还是新鲜货,Git还没有被发明,Skype这类VoIP的方案还没有出现,云还仅仅是用来下雨的。因此里面说到

在开发小组中最有效率也最有效果的信息传达方式是面对面的交谈

当然,值得一提的是强调沟通本来就不是Agile的发明。在Agile Manifesto刚刚被编写出来时还占据着主流的瀑布式开发里,要求编码开始前撰写非常详细的文档,然后再对这些文档进行充分的评审:沟通和讲解其实是完成这些工作的前提。只不过Agile不但高度推崇面对面的交流,而且鄙视文档活动,结果虽然在一定程度上减少了瀑布流程里面写文档写到程序员晕厥的状况,但又对公司造成了新一轮的伤害。

讨论之后还得多写写

在软件开发中,有些工作通过口头交流本身就是低效的。

当然有适合面对面讨论的部分:比如对关键模块的技术选型,比如对业务流程和需求的澄清。

但进入设计和实现阶段的活动,应该是看得见摸得着版本化可回溯的,所谓"a wireframe is worthy than one thousand words,a prototype is worth a thousand wireframes"。

比如下面是一次提交之后Gitlab上提供的查看diff的界面:通过这样的方式review整个改动,比小伙伴坐在自己怀里结对编码要清楚得多(而且小伙伴坐在怀里的时候引发的羞赧常常让你难以把他的错误直接打到他脸上不是么):

Vhost threshold

所以我会经常在公司里面鼓动大家把输出都落到代码和文档里面。经过一段时间,就会慢慢看到有人在群里面问“那个什么什么是怎么回事”的时候,后面的回复是“你去看看confluence上xxx页面”或者是“这个是jira的xxx问题单讨论的”。

Vhost threshold

如果你的员工经常需要重复回答同一个问题,包括来个新员工这个环境怎么配那个Wifi的密码还需要人告诉他/她,你也好意思说自己是敏捷的?

讨论之前最好多写写

强调面对面沟通,而弱化文档和代码本身在沟通中的作用,造成的一个更严重后果是面对一个问题,大家不仔细思考就开会讨论。结果会上随便放炮,下来各不认账。

我在M记做PM的时候,项目组里面有几位韩国同事。因为是远程办公,每天除开代码之外,我们只能通过Skype交流。这里面有个叫Jason的同事,无论分给他什么活,他不但做得飞快,而且还会以Confluence上一篇甚至几篇洋洋洒洒的文章作为交付。

在回顾那个项目的时候我发现,我和他没有每日站会,没有Sprint结尾的demo,但我们之间的沟通是整个团队里面最高效的:每天查看他提交的代码和文档,我就非常清楚他的进度和问题了。

这样做的收益并不仅仅是我们之间沟通的高效。

在他第三个小孩儿出生之后一周,他搞定了一个非常复杂的调研任务。聊天的时候我问他怎么可以在那么多私事需要处理的情况下弄得这么快。他说,有很多时候在confluence上写着写着,自己的思路就清晰了。

这也是我自己的感受。很多次我在写邮件问其他人问题的时候,邮件写完自己就有了答案。我自己呆过的团队,厉害的工程师都非常能写:他们的区别不过是有些人只记录给自己看,有些人会写给大家看。

流程、工具和个体究竟谁更重要

前面说了技术的革新使“面对面沟通”的重要性变得过时和有害。那么下面这个Agile核心思想呢:

个体和个体间的交流比流程和工具更重要

我自己对这种“人定胜天”的论调天生有抗拒感。就像当年主席发明这句话是因为大家日子过得足够糟一样,只有你为团队提供的工具足够糟才需要这么去忽悠大家。

软件开发是一项和工具高度相关的工作。除去你的生产活动的效率很大程度上取决于你对工具的熟悉程度以外,你还需要使用工具参与到流程中:和其他人交流、配环境、提单、解bug、记录工作时间等等,都离不开工具。

无论你的团队好好工作的意愿多么强烈,如果你还在用sametime而不是slack,还在用破破烂烂自己开发的测试用例管理工具而不是rally,你的开发流程就是不如别人顺畅。

因为使用的工具可以“塑造”你的团队沟通的方式(反过来你团队沟通的方式也可以塑造他们使用工具的方式)。

这也就是Marshall McLuhan的著名论断The medium is the messageWired杂志把他视为办刊的精神导师,我觉得搞互联网的人都该看看他的书):

Vhost threshold

Daily StandUp or Daily FuckUp

市场销售人员和开发人员应该在整个项目过程中每天都在一起工作

首先,严格的区分市场销售人员和开发人员本身就是个糟糕的主意。

其次,“每天都在一起”也是奇怪的号召,而在Scrum流程中,这种奇怪的号召被具体化,成了每日站会。我经常在参与站会的时候听到小兄弟们说的其实是“我昨天说我要三天做完的事情,真的还要两天”。作为职业玩家,职业程度很多时候就体现在不需要每天都告诉其他人自己要怎么做。可以想象一下830每天开个站会,然后梅西说,我今天可能需要在训练里面给伊涅斯塔传5个过顶球,你做好胸部停球转身抽射的准备……

敏捷文化

我本身是挺讨厌“方法论”者和他们发明的术语的。当然,可能也不是我一个人讨厌。参与了Agile Manifesto制定的Dave Thomas在Agile Is Dead里面说过:

I haven’t participated in any Agile events, I haven’t affiliated with the Agile Alliance, and I haven’t done any “agile” consultancy. I didn’t attend the 10th anniversary celebrations.

Why? Because I didn’t think that any of these things were in the spirit of the manifesto we produced...

The word “agile” has been subverted to the point where it is effectively meaningless, and what passes for an agile community seems to be largely an arena for consultants and vendors to hawk services and products.

在我看来,就好比真正明白某个知识的人总是能用大白话把你讲明白一样,在敏捷开发流程里面被某些公司鼓吹的那些活动和术语在我看来都是些没有价值的东西(ThoughtWorks,说你呢!)。

比如Scrum,我从来不说我们用Scrum,而说我们搞迭代(如果和老外我也说iteration而不是Scrum)。 比如Sprint,我从来不喜欢说我们这个Sprint,而是说我们这个迭代。 迭代本身是个有语义的好词语,为什么不用它呢。

就像Gregg Caines在这篇文章里面说的一样:

when you want to get people to change the way they work, and you want them to understand the completely foreign concepts you’re bringing to them, it’s absolutely crucial that you name the thing in a way that also explains what it is not.

然后他还说:

In Scrum, it’s also common to have a “sprint commitment” where the team “commits” to a body of work to accomplish in that time frame. The commitment is meant to be a rough estimate for the sake of planning purposes, and if a team doesn’t get that work done in that time, it tries to learn from the estimate and be more realistic in the next sprint. Developers are not supposed to be chastized [sic] for not meeting the sprint commitment — it’s just an extra piece of information to improve upon and to use for future planning. Obviously naming is hugely important here too, because in every other use of the word, a “commitment” is a pledge or a binding agreement, and this misnomer really influences the way people (mis)understand the concept of sprints. Let’s face it: if people see sprints as just more frequent deadlines (including those implementing them), the fault can’t be entirely theirs.

的确,问工程师要个estimation然后把它当成commitment,这不是耍流氓么。不仅仅是Scrum,大多数的组织里面推行一年半载的敏捷流程,大多数人还是对它究竟每个阶段在干什么迷迷糊糊。即便是靠培训敏捷流程混饭的公司也承认要把他们鼓吹的流程落地是非常难的:

In our experience, it takes a team two to six months to become fluent at the one-star level. About 45% of the teams we talk to say they’re fluent at this level.

Reaching the two-star level takes another three to 24 months, depending on the amount of technical debt in the code. About 35% of teams report fluency at this level.

Three-star teams are much more rare. About 5% of teams report fluency at this level. We’ve heard reports ranging from a year to five years to reach this level of fluency.

我们的选择

敏捷开发的很多思想是有益的,但我们没有用Scrum和它那堆奇怪的活动(standup、sprint planning等等)。我们鼓励少开会,多通过异步的方式沟通而不是经常让大家停下手里的工作来进行讨论。我们也从来不对estimation之类的东西认真,更不去做什么burndown或者计算点数,因为Agile Manifesto里面说过:

可以工作的软件是进度的主要度量标准

自动化测试、持续集成、自动部署、有效的监控和运维,让你的软件随时可以发布,才是产品可以不断演进的根基。