设计的威力 - 什么才是更好的WEB框架

3 views
Skip to first unread message

v cc

unread,
Jul 4, 2008, 11:38:53 AM7/4/08
to pyth...@googlegroups.com
最近看多了这样的言论:用一个最基本的,再自己用最好的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的设计:
class MyModel(models.Model):
       name = models.CharField(_('名称'),max_length=20)
 
用于页面显示时的字段名,做i18n多容易实现啊!从这里就看出设计的威力出来了吧!
 
我是怎样解决trac的i18n问题的?说起来也挺恶心,我在ticket的模板里这样干:
先定义一个函数:
    <py:def function="ZhFieldName(name)">
        <py:choose test="">
          <py:when test="name == 'type'">类型</py:when>
   <py:when test="name == 'milestone'">里程碑</py:when>
   <py:when test="name == 'priority'">优先级</py:when>
   <py:when test="name == 'keywords'">关键字</py:when>
   <py:when test="name == 'version'">版本</py:when>
   <py:when test="name == 'component'">组件</py:when>
   <py:when test="name == 'cc'">抄送</py:when>
   <py:when test="name == 'description'">描述</py:when>
   <py:when test="name == 'summary'">总结</py:when>
   <py:when test="name == 'owner'">属主</py:when>
   <py:when test="name == 'status'">状态</py:when>
   <py:otherwise>${name}</py:otherwise>
 </py:choose>
    </py:def> 
 
再在取field.name时这样干:
${ZhFieldName(field_name)}
 
ticket的form是这样的:
        <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>
        </fieldset>
 
这是不是更好了?我感觉一点都不好。这就是大名鼎鼎的genshi模板系统,有人认为它是最好的模板系统。可是写的template真的好难读啊,就连我这么有经验的开发人员看着都觉得头大。这也不能怪genshi,它只是一个模板语言啊,更高层的Form之类的设计不是它应该干的活啊。如果用django的newforms来实现,那可得多省心啊。
 
瞧我这补丁打的,这还算简单了,至少在模板里就搞定,不用去碰它的代码。但是如果做过好的i18n的设计,根本就不会碰到这个问题;如果对HTML表现层做过好的设计,也绝对不用写这样难看懂的template。
 
我最近还干过一件事,花了一个下午改django-pyodbc的backend到django最新开发版。django的数据库层已经做过重构,清晰了许多,让我也没费太大力的就完成了,这就是出色设计所带来的威力!
 
什么是更好的WEB框架,我相信每个人心里都有数了。让我们更靠谱一点,谦虚一点,好好吸收优秀设计的营养,说不定哪一天,我们也可以设计出"更好"的框架来! ^_^
 
vcc
_
没有最好,只有更好!
 
 
 

Zoom.Quiet

unread,
Jul 4, 2008, 11:51:44 AM7/4/08
to pyth...@googlegroups.com, 哲思py, ZPyUG~珠江三角区Py用户组, CPUG-华东南用户组, SPyUG~上海及长江三角区Py用户组
收录在: http://wiki.woodpecker.org.cn/moin/MiscItems/2008-07-04

2008/7/4 v cc <vcc...@gmail.com>:

--

http://zoomquiet.org'''
过程改进乃是催生可促生靠谱的人的组织!
PE keeps evolving organizations which promoting people be good!'''

Cliff Peng

unread,
Jul 4, 2008, 8:56:26 PM7/4/08
to pyth...@googlegroups.com


2008/7/4 v cc <vcc...@gmail.com>:

最近看多了这样的言论:用一个最基本的,再自己用最好的Class来组合扩展,就是一个更好的框架,什么MVC、ORM、Cache、I18N等等,都是很容易加的,..... 深不以为然。
 
为了避免不必要的误解,首先定义下什么是"更好"的WEB框架。顾名思义,更好就是比现有所有框架都要好。它不是指你在XXX上加了一个功能比XXX好就是更好,也不是你喜欢它就是更好。也许某个框架更适合你,但不能称它是更好的框架。
 
OK,言归正传。说把一大堆Class,哪怕它们单个都是"最好"的class,拼凑在一起就比一个从一开始就非常完整、精妙设计的框架更好听起来实在有点离谱。任何一个稍微有点软件工程概念的人都知道这是不可能的。
 
不服?好,我们用事实说话。MVC、ORM、Cache、I180N很容易加吗?非也。最近把trac应用到公司的开发管理中,大名鼎鼎的trac

这么快就说服老板用python了?,佩服佩服

这招叫"潜移默化"吧?嘿嘿
 

匡东

unread,
Jul 4, 2008, 9:25:04 PM7/4/08
to pyth...@googlegroups.com
其实django的各模块之间耦合性很弱,比如template和db,基本上是独立的,可以独立地用。好的webframework,各部分基本上是相互独立的,但也有协调统一的部分。我觉得对webframework来说,组合的策略不失为一种好的策略,GAE就是个很好的例证。事实上我并不觉得设计不重要,设计处于"食物链"的下层,设计"中毒"了,后边也会跟着"中毒",采用组合的策略并不意味着排斥设计,就像GAE也并不排斥设计一样,GAE显然是经过精心设计的。



2008/7/5 Cliff Peng <szha...@gmail.com>:



--
如切如磋,如琢如磨。

mifly

unread,
Jul 6, 2008, 8:59:01 PM7/6/08
to python-cn`CPyUG`华蟒用户组
需要什么功能就去实现,不需要的就不要加,等到需要的时候再加。用一个最基本的,再自己用最好的Class来组合扩展,这个思想是符合敏捷开发原则的。
需要加功能,并不是加就行了,还需要重新设计,重构原来的代码重新实现。你举的例子说加功能很不容易,也许就是因为原来的代码并不是最好的设计,因为开
始就没有考虑这些功能的整合,设计并不是很好的。
重构之前需要设计。不需要的功能一开始就考虑,就会增加复杂性和引起过度设计。还是觉得要什么就加什么吧。重构再重构,迭代再迭代,好系统就这样出来
了。

v cc

unread,
Jul 7, 2008, 10:01:11 AM7/7/08
to pyth...@googlegroups.com
2008/7/7 mifly <mifl...@gmail.com>:

需要什么功能就去实现,不需要的就不要加,等到需要的时候再加。用一个最基本的,再自己用最好的Class来组合扩展,这个思想是符合敏捷开发原则的。
需要加功能,并不是加就行了,还需要重新设计,重构原来的代码重新实现。你举的例子说加功能很不容易,也许就是因为原来的代码并不是最好的设计,因为开
始就没有考虑这些功能的整合,设计并不是很好的。
重构之前需要设计。不需要的功能一开始就考虑,就会增加复杂性和引起过度设计。还是觉得要什么就加什么吧。重构再重构,迭代再迭代,好系统就这样出来
了。
 
敏捷是需要框架支持的,如同你放着Django、ROR这样的敏捷框架不用,非要自己从头写一个还称之为"敏捷",恐怕无论如何都说不过去吧。
 
重构再重构,迭代再迭代,也要看重构的是什么,迭代的是什么。敏捷是非常强调效率的,尽可能的减少重构次数、缩短迭代周期,不正是敏捷的追求吗?
 
vcc
-
 
 
Reply all
Reply to author
Forward
0 new messages