V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
keakon
V2EX  ›  Project Babel

稍微提几个可改进之处

  •  
  •   keakon · 2010-11-17 16:57:45 +08:00 · 5410 次点击
    这是一个创建于 4910 天前的主题,其中的信息可能已经有所发展或是发生改变。
    粗读了下Babel的源码,发现了一些问题。不过我说话比较直,意见可能会很难听。

    首先是TopicHitHandler和PageHitHandler这2个用于计数的handler没有做事务处理。
    如果一个主题同时被多个人浏览的话,会很容易冲突。而如果此时这个主题进行了其他修改,这些修改也很可能丢失。
    至于其他修改的地方,我就没去找了,至少last_reply_by、last_modified这些字段就是为修改存在的。

    其次是很多地方的性能可以做优化。
    最明显的是很多模型的num字段实际上可以用key id来代替,get_by_id比query将近快1个数量级。
    此外还有很多索引是可以去掉的,例如Member.password我就想不出要index的理由。
    另外,GqlQuery的解析速度是很慢的(比Query慢20倍,还得小心被GQL注入),通常都会使用bind来重用的。

    最后是代码风格。
    我觉得model和它的操作应该写在一起,作为方法或类方法。但是Babel却将其分散在各处,有的是方法,有的是v2ex.babel.da里的函数,有的是直接写在controller中。
    这种写法当然也是OK的,不过不知道Livid是否觉得维护起来很麻烦。如果新增或修改的功能涉及到model的变化,不得不到处搜索这些model、model name的usage,甚至memcache key。

    而且由于代码的分散,可重用性也因而降低,因此Handler显得过于冗长,很难一眼看出做了哪些操作。
    例如HomeHandler.get,恐怕没人能在30秒内看懂它做了哪些事————然而读完却发现所做之事实际上是很简单的。
    至少在区分是否手机时,取数和渲染逻辑是大致一样的,很显然只要把memcache key和template file(或者直接用browser['ios'])作为参数就能提取出一个函数了。


    意见就说这么多,总体来说Project Babel给我的感觉更像是一个从PHP以最快方式(或者说最小努力)移植到GAE/Python的项目,而不是针对GAE去设计的项目。
    我觉得Livid应该在适当的时机尽早重构代码,不然随着功能的增多,维护势必会越来越难。
    23 条回复    1970-01-01 08:00:00 +08:00
    Livid
        1
    Livid  
    MOD
       2010-11-17 17:18:54 +08:00
    关于 .key().id() 的问题,可能是因为我还没有完全理解及实验充分。但是我看到的问题是:

    1. 这个东西貌似不是加一递增的?

    2. 可以自己指定么?

    保留传统的自增 id 是 V2EX 在规划时我觉得必须要有的一个功能,于是就用了 counter + num 的方式实现。
    Livid
        2
    Livid  
    MOD
       2010-11-17 17:22:07 +08:00
    把和 model 有关的东西写在 model 里我非常同意,这也是以后的代码方向。
    Livid
        3
    Livid  
    MOD
       2010-11-17 17:26:11 +08:00
    HomeHandler 在 /my/ 及相关设置做好之后会重构。

    代码这样的东西,说到底是为运营服务的。

    有的时候,为了上线的时间和运营的需要,我可能更多关心的是如何让一个能跑的东西尽快 up and running,代码的美观性不是我的主要考虑因素。

    而且代码风格这样的东西,当你同时在用多种语言编程的时候,比如我现在每天都会用到 C / Objective-C / PHP / Python,有的时候确实稍不注意就会出现很奇怪的合体产物了。
    Los
        4
    Los  
       2010-11-17 17:26:26 +08:00
    嗯,建议逻辑上尽量减少if之类语句的嵌套,这样逻辑会清晰不少
    c
        5
    c  
       2010-11-17 18:21:07 +08:00
    有些东西在很久前都建议过,没有菜我,也许我太菜了吧。

    不过也无所谓,存在即为合理
    c
        6
    c  
       2010-11-17 18:22:34 +08:00
    key().id()不能自定义,
    可以用key().name()啊,这个只是稍微比key().id()慢那么一点点。
    keakon
        7
    keakon  
    OP
       2010-11-17 18:55:56 +08:00
    @Livid

    key id可以自定义,例如构造一个Topic实体,id为123:
    Topic(key=db.Key.from_path('Topic', 123))

    不过,非要以1递增不知道有什么用意,似乎排序时不需要按id来排列,都是以时间来排序的

    不使用事务或加双重锁的话,2个人同时post 2个topic时,很可能会覆盖掉其中一个
    c
        8
    c  
       2010-11-17 19:04:13 +08:00
    @keakon 我又去官方看了下文档,发现key().name()和key().id()基本一样?
    Livid
        9
    Livid  
    MOD
       2010-11-17 19:05:54 +08:00
    @keakon 理论上来说确实是这样的。但是目前这样的情况还没有发生过。
    Livid
        10
    Livid  
    MOD
       2010-11-17 19:07:47 +08:00
    @c 所以我对你开始做自己的论坛程序这件事情,感到非常赞赏。做比说更有价值。

    欢迎大家关注 @c 的作品:

    http://xfox.appspot.com/

    我暂时还没有时间去看 @c 的代码,不过我相信他肯定比我写得好。
    c
        11
    c  
       2010-11-17 19:17:23 +08:00
    @Livid xFox不会再继续开发了,因为有些问题解决不了 :) 所以现在把2008年的一个相册拿出来重写了一下。

    我不认为我的代码比你的好,我只是认为你的代码本来可以写的更优雅一点,因为将来会有很多人看你的代码。
    keakon
        12
    keakon  
    OP
       2010-11-17 19:19:32 +08:00
    @c 不一样,你用get_by_id()和get_by_key_name()取一下就会发现,1和'1'都可以存在,并且表示的是2个不同的实体。

    此外用key_name构造的key会比id要大,且随key_name长度而增长,而id是定长的int64。

    实际上我习惯用key_name来减少一个唯一字段,找不到这种字段时,我才会用id

    例如用户类,id对我来说没有任何作用,那么我就会拿用户名或email这种唯一字段来做key_name;而文章类虽说可以强制要求URL唯一,但URL是有可能去改动的,所以仍然只能选择id
    darasion
        13
    darasion  
       2010-11-17 19:19:49 +08:00
    话说,事务处理这个地方我一直就没弄明白。

    到底谁和谁一起可以做一个“Entity Group”? 这个我总是搞不懂。。。
    Livid
        14
    Livid  
    MOD
       2010-11-17 19:21:31 +08:00
    @keakon 个人页面上的“V2EX 的第 # 号注册会员”这个功能,在最早那个版本的 V2EX 上就有,所以这次在 GAE 上重建时,我当然希望尽可能复现以前所有的要点。
    c
        15
    c  
       2010-11-17 19:23:06 +08:00
    @keakon 因为我喜欢用漂亮的slug,所以我原来一直用key_name,这两个效率应该是一样的吧?
    keakon
        16
    keakon  
    OP
       2010-11-17 19:29:46 +08:00
    只能说效率几乎是一样的,但是key_name生成的key比较大,因此实体及其索引(每个索引都包含key)也会多占用一些空间

    此外key只能get,在query时基本上没什么用处,所以如果想取key name是'1'开头的实体就很难了,至少文档里没有介绍__key__比较是否能用于这种情况
    c
        17
    c  
       2010-11-17 19:33:48 +08:00
    @keakon 我一般都会建立一个字段,比如slug来保存key_name的,这样你上面说的问题也可以解决了。

    主要是我喜欢优美的URL,现在不是要让URL有意义吗?
    keakon
        18
    keakon  
    OP
       2010-11-17 19:57:51 +08:00
    @c
    这样就不得不多占用存储空间了。而且正如前面所说,假如你的实体生成以后,突然发现slug里有错别字,不得不更改,你就只能删掉重新创建一个实体了。而如果这个实体还是根实体的话,整个实体组可能都得重新创建。

    @darasion
    在构造实体(准确来说是它的key)时可以指定一个实体为它的父实体。而且这个实体也可以作为其他实体的父实体。
    由于一个实体最多只能有1个父实体(但是可以有多个子实体),所以一直向上总能找到一个没有父实体的实体,它就是这个实体组的根实体。
    在单个事务中,你只能更改1个实体组里面的实体。

    举例来说,如果把Reply的父实体定为Topic的话,用户在post一个reply时,就能在事务中完成创建Reply实体并将它的父实体Topic的reply字段加1。
    而如果它们不在一个实体组,你就只能先put一个Reply实体,然后再找到对应的Topic实体,再给它的reply+1。而如果你Reply实体put成功,Topic实体put失败,那么就存在不一致了。
    Google给出的方式是分离事务,也就是用一个task来执行Topic的保存,因为task在失败时会自动重试,直到成功。

    更常见的例子,如果不使用事务的可能造成这种情况:
    1.一个用户a的访问使得你得给Topic的hit加1,于是你取出了这个topic。
    2.同时,另一个用户b也要编辑这个Topic的content字段,于是也取出了这个topic
    3.b编辑完了,保存成功。
    4.a的hit += 1执行完了,也进行保存,但是这个topic的content字段是a取出来时的内容,于是这次保存就让b的编辑无效了。

    而单个实体本身就是一个实体组,因此自然可以使用事务,它就可以保证2个人取出数据到保存成功这个过程是串行的,相互之间不会覆盖。
    darasion
        19
    darasion  
       2010-11-17 20:14:00 +08:00
    @keakon 谢谢解答。

    还有几个问题,

    1、如果我想在一个方法里边更新很多类( Kind ),而不是one Topic-many Reply这种简单的形式,那么这样的实体组怎样规划?有没有一些模式可以套用?

    2、在一个已经存在的,没有规划 事务/实体组 的项目中,引入 事务/实体组 后,应该如何迁移已经保存了的数据?能平滑过渡吗? (以one Topic-many Reply 为例)。
    c
        20
    c  
       2010-11-17 20:19:53 +08:00
    @keakon GAE的数据不是推荐的冗余吗,所以能通过冗余解决的问题都冗余。第二个slug不能修改的问题,这的确是个问题。
    keakon
        21
    keakon  
    OP
       2010-11-17 20:20:47 +08:00
    1. 使用task queue。一个实体保存成功后创建另一个task来更新下一个实体。文档里好像有个例子。

    2. 实体一旦保存到数据库,它的实体组关系就不能变更了。因此你只能下载所有实体,删除所有实体,然后在上传时通过设置key的parent来构造实体组。
    c
        22
    c  
       2010-11-17 20:27:57 +08:00
    darasion
        23
    darasion  
       2010-11-17 21:07:01 +08:00
    谢 @keakon @c 二位。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   984 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 19:21 · PVG 03:21 · LAX 12:21 · JFK 15:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.