稍微提几个可改进之处

2010-11-17 16:57:45 +08:00
 keakon
粗读了下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应该在适当的时机尽早重构代码,不然随着功能的增多,维护势必会越来越难。
5528 次点击
所在节点    Project Babel
23 条回复
Livid
2010-11-17 17:18:54 +08:00
关于 .key().id() 的问题,可能是因为我还没有完全理解及实验充分。但是我看到的问题是:

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

2. 可以自己指定么?

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

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

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

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

不过也无所谓,存在即为合理
c
2010-11-17 18:22:34 +08:00
key().id()不能自定义,
可以用key().name()啊,这个只是稍微比key().id()慢那么一点点。
keakon
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
2010-11-17 19:04:13 +08:00
@keakon 我又去官方看了下文档,发现key().name()和key().id()基本一样?
Livid
2010-11-17 19:05:54 +08:00
@keakon 理论上来说确实是这样的。但是目前这样的情况还没有发生过。
Livid
2010-11-17 19:07:47 +08:00
@c 所以我对你开始做自己的论坛程序这件事情,感到非常赞赏。做比说更有价值。

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

http://xfox.appspot.com/

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

我不认为我的代码比你的好,我只是认为你的代码本来可以写的更优雅一点,因为将来会有很多人看你的代码。
keakon
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
2010-11-17 19:19:49 +08:00
话说,事务处理这个地方我一直就没弄明白。

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

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

主要是我喜欢优美的URL,现在不是要让URL有意义吗?
keakon
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
2010-11-17 20:14:00 +08:00
@keakon 谢谢解答。

还有几个问题,

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

2、在一个已经存在的,没有规划 事务/实体组 的项目中,引入 事务/实体组 后,应该如何迁移已经保存了的数据?能平滑过渡吗? (以one Topic-many Reply 为例)。
c
2010-11-17 20:19:53 +08:00
@keakon GAE的数据不是推荐的冗余吗,所以能通过冗余解决的问题都冗余。第二个slug不能修改的问题,这的确是个问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/4640

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX