最近看多了这样的言论:用一个最基本的,再自己用最好的Class来组合扩展,就是一个更好的框架,什么MVC、ORM、Cache、I18N等等,都是很容易加的,..... 深不以为然。
为了避免不必要的误解,首先定义下什么是"更好"的WEB框架。顾名思义,更好就是比现有所有框架都要好。它不是指你在XXX上加了一个功能比XXX好就是更好,也不是你喜欢它就是更好。也许某个框架更适合你,但不能称它是更好的框架。
OK,言归正传。说把一大堆Class,哪怕它们单个都是"最好"的class,拼凑在一起就比一个从一开始就非常完整、精妙设计的框架更好听起来实在有点离谱。任何一个稍微有点软件工程概念的人都知道这是不可能的。
不服?好,我们用事实说话。MVC、ORM、Cache、I180N很容易加吗?非也。最近把trac应用到公司的开发管理中,大名鼎鼎的trac啊,trac的开发团队还开发着一大堆好的不得了的东东啊,例如genshi模板系统,厉害得不得了。虽然他们还开发了一个专门做I18N的Babel,然而很不幸,trac的i18就有问题,莫名其妙的问题,i18n不完整。最重要的ticket这个页面居然有些字段不能i18n!!! 为什么?这就是一个开始没做过完整设计而不断的打补丁来实现新功能的结果。
trac组合了sqlalchemy这个目前看来也许是"最好"的Python ORM,然而很不幸,sqlalchemy却不合用,因为它不能为定义的字段定义一个label来你在页面显示的时候用。这也不能怪sqlalchemy,因为它不是为web框架设计的,它不需要加这些多余的东西,加了这些东西它反而不是一个干净的ORM了。看看Django的设计:
<fieldset id="properties"
py:if="'TICKET_CHGPROP' in perm(ticket.resource) or
(not ticket.exists and 'TICKET_CREATE' in perm)"
py:with="fields = [f for f in fields if not f.skip]">
<legend>${ticket.exists and 'Change ' or ''}Properties</legend>
<table>
<tr>
<th><label for="field-summary">Summary:</label></th>
<td class="fullrow" colspan="3">
<input type="text" id="field-summary" name="field_summary"
value="$ticket.summary" size="70" />
</td>
</tr>
<py:if test="'TICKET_ADMIN' in perm(ticket.resource)">
<tr>
<th><label for="field-reporter">Reporter:</label></th>
<td class="fullrow" colspan="3">
<input type="text" id="field-reporter" name="field_reporter"
value="${ticket.reporter}" size="70" />
</td>
</tr>
</py:if>
<py:if test="'TICKET_EDIT_DESCRIPTION' in perm(ticket.resource) or not ticket.exists">
<tr>
<th><label for="field-description">Description:</label></th>
<td class="fullrow" colspan="3">
<textarea id="field-description" name="field_description"
class="wikitext" rows="10" cols="68"
py:content="ticket.description"></textarea>
</td>
</tr>
</py:if>
<tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')"
py:with="fullrow = len(row) == 1">
<py:for each="idx, field in enumerate(row)">
<th class="col${idx + 1}" py:if="idx == 0 or not fullrow">
<label for="field-${
field.name}" py:if="field"
py:strip="field.type == 'radio'">
${ZhFieldName(field.edit_label or field.label or
field.name)}:
</label>
</th>
<td class="col${idx + 1}" py:if="idx == 0 or not fullrow"
colspan="${fullrow and 3 or None}">
<py:choose test="field.type" py:if="field">
<select py:when="'select'" id="field-${
field.name}" name="field_${
field.name}">
<option py:if="field.optional"></option>
<option py:for="option in field.options"
selected="${ticket[
field.name] == option or None}"
py:content="option"></option>
<optgroup py:for="optgroup in field.optgroups"
label="${optgroup.label}">
<option py:for="option in optgroup.options"
selected="${ticket[
field.name] == option or None}"
py:content="option"></option>
</optgroup>
</select>
<textarea py:when="'textarea'" id="field-${
field.name}" name="field_${
field.name}"
cols="${field.width}" rows="${field.height}"
py:content="ticket[
field.name]"></textarea>
<span py:when="'checkbox'">
<input type="checkbox" id="field-${
field.name}" name="field_${
field.name}"
checked="${ticket[
field.name] == '1' and 'checked' or None}" value="1" />
<input type="hidden" name="field_checkbox_${
field.name}" value="1" />
</span>
<label py:when="'radio'"
py:for="idx, option in enumerate(field.options)">
<input type="radio" name="field_${
field.name}" value="${option}"
checked="${ticket[
field.name] == option or None}" />
${option}
</label>
<py:otherwise><!--! Text input fields -->
<py:choose>
<span py:when="field.cc_entry"><!--! Special case for Cc: field -->
<em>${field.cc_entry}</em>
<input type="checkbox" id="field-cc" name="cc_update"
title="This checkbox allows you to add or remove yourself from the CC list."
checked="${field.cc_update}" />
</span>
<!--! Cc: when TICKET_EDIT_CC is allowed -->
<span py:when="
field.name == 'cc'">
<input type="text" id="field-${
field.name}"
title="Space or comma delimited email addresses and usernames are accepted."
name="field_${
field.name}" value="${ticket[
field.name]}" />
</span>
<!--! All the other text input fields -->
<input py:otherwise="" type="text" id="field-${
field.name}"
name="field_${
field.name}" value="${ticket[
field.name]}" />
</py:choose>
</py:otherwise>
</py:choose>
</td>
</py:for>
</tr>
</table>
这是不是更好了?我感觉一点都不好。这就是大名鼎鼎的genshi模板系统,有人认为它是最好的模板系统。可是写的template真的好难读啊,就连我这么有经验的开发人员看着都觉得头大。这也不能怪genshi,它只是一个模板语言啊,更高层的Form之类的设计不是它应该干的活啊。如果用django的newforms来实现,那可得多省心啊。
瞧我这补丁打的,这还算简单了,至少在模板里就搞定,不用去碰它的代码。但是如果做过好的i18n的设计,根本就不会碰到这个问题;如果对HTML表现层做过好的设计,也绝对不用写这样难看懂的template。
我最近还干过一件事,花了一个下午改django-pyodbc的backend到django最新开发版。django的数据库层已经做过重构,清晰了许多,让我也没费太大力的就完成了,这就是出色设计所带来的威力!