分类: ‘编程随笔’ 的所有文章

关于要不要加测试接口的讨论

2012年12月23号,星期日 作者:半瓶墨水   链接:http://www.2maomao.com/blog/why-we-need-test-hook/

上周跟一个资深老员工讨论,很惊讶他的论调的同时,听到了一段精彩的讨论。

事情是这样的,我在给自己的代码写集成测试的时候,需要检查系统的一个内部状态,虽然可以通过其他方式,但是这样做会影响到系统本身,可能会让测试结果表现得不稳定。这个模块是资深老员工A负责的,所以我跟他说加一个接口以便测试。

老A强烈反对,说我绝不为了方便测试写代码(奇怪吧,我也奇怪,居然还有许多人这样想的)。

没办法,找来了组里Architect L来调停,下面是对话:

L:我们需要加这个接口。
A:不需要,我绝不为了方便测试写代码。我只关注功能。
L:需要。否则测试会三天两头出错,不稳定,跑一次没过,跑一次过了。。。到时候出了问题你去看。
A:测试失败大家都看,这是整个组的责任。
L:上个项目测试失败的时候你去看过几次?
A:。。。
L:我们需要加这个接口。
A:我不能为了方便测试写代码。比如作为一个医生,我不能对病人说,需要把你的心脏拿出来单独测试一下一秒钟跳一次会怎么样,然后拿出肝脏看看造血功能。。。

。。。众人狂笑,L跟着笑完这么说。。。

L:但是我们不造人,如果我们造人,我们或许真的把心脏单独测试一下,把肝脏单独测试一下。
L:比如说我们现在生产汽车,我们不能总把所有的零件兑到一起直接上路测试,要单独测试螺丝,轮胎,引擎。。。然后才是上路测试,这样一旦出了问题才好查。
A:。。。

。。。然后大家开始讨论怎么做。。。

这种讨论,闲下来谁都能想清楚,但是我看L就简单仰头思考了一秒,就说的这么清楚,很是佩服。当然如果用机器人做例子就更好了。

L是我最佩服的Architect,没有之一。几乎每次遇到百思不得其解的问题,到他那儿玩儿一样就解决了。
更多的时候,你去找他,聊完之后,豁然开朗,但是之前思考了一天干了半天的活要重新来过,所以有些同事现在都有点害怕跟他聊自己的工作细节。这几个月我有闲暇,几乎天天跟他聊,项目,设计,软件行业,软件公司,甚至还扯到国家人口问题,在每一个主题上,他的思维都很逻辑化,很快。活了三十多岁了,第一次觉得自己的脑子明显不够用。

Python GIL(Global Interpreter Lock)

2012年05月20号,星期日 作者:半瓶墨水   链接:http://www.2maomao.com/blog/python-gil/

*Why We Need GIL*

1. GIL enforces no parallel python binary code running in same process

2. This is good for C library extension
— means the library will not been running in parallel in 2 python threads in one process
— make quite a few things more simpler(need to use event/lock otherwise)
— even if you “don’t write C extension”, a lot of libraries are implemented in C

3. python Garbage Collection is reference counting + cycle-reference removal
— it depend on GIL in order to be consistent

*When does GIL behaves BADLY*

1. It can’t leverage multi-core CPUs when doing CPU extensive tasks
— this can be mitigated by using C extension libs like NumPy
— if you are doing IO (network/file, etc) in between, then it won’t be a huge problem

2. it sometimes blocks Ctrl+C on command line, you had to kill -9 sometimes

*References*
http://en.wikipedia.org/wiki/Global_Interpreter_Lock
http://wiki.python.org/moin/GlobalInterpreterLock
http://www.dabeaz.com/GIL/

折腾 – 发芽网后台更新Python2.7.2, Django1.3, Pygments1.4, jQuery1.6.2

2011年08月21号,星期日 作者:半瓶墨水   链接:http://www.2maomao.com/blog/bluehost-python272-django13-pygments14-jquery162/

发芽网后台更新了:Django1.3, Python2.7.2,jQuery1.6.2,Pygments1.4

其中Pygments的更新带了更多的语法高亮支持,包含了许多人熟悉的AutoHotkey

话说,似乎是临时的,很多的国外网站都没法访问了,连ssh也不可用了,这些网站都是技术性网站,应该没问题的,加上vpn就可以访问了,奇怪的是,用vpn也访问不了twitter了,难道传说中的白名单制度开始试水了?Fuck GFW! @ 2011-08-21, 18:47:11

—————————————————————

最近有人在代码发芽网上留言问能不能加上AutoHotkey的高亮支持,瞄了一眼Pygments的新版本1.4,发现已经支持了,打算搞过来。另外发现发芽网后台的Django和Python以至于jQuery都老了,想起前几天看到阮一峰介绍新版jQuery性能的文章,据说快了很多,决定把这些一起更新一下,折腾开始了,且看一个业余的网页开发者会遇到多少问题。。。

1. 首先,参考以前的文章”configure your own python 2.5.2 on bluehost”安装Python2.7.2,下载编译安装,搞定。顺手改了.bashrc,这样下次ssh到服务器用的就是2.7了

2. 然后,下载easy_install的egg包,用 sh xxx.egg 安装完毕,然后,easy_install安装了发芽网依赖的库:django1.3, PIL, flup, MySQL-Python, Pygments1.4, markdown2, python-openid,为了用起来顺手,又安装了ipython

3. 然后,参考以前的文章Bluehost Django fastcgi配置, 静态(static)文件处理更改了t.fcgi,指向新的python库

4. 然后到后台杀死所有t.fcgi进程,网站立马不可访问了,500 Error或者干脆半天都不返回,杯具。

5. 查吧,一个个查,根据多次折腾的经验,觉得应该是t.fcgi里面出异常了。这个好办(现在说好办,当时也想了半天),因为t.fcgi也是python脚本,直接执行之,居然没有发现任何问题,突然发现,t.fcgi最上面一行,对,就是指定python可执行文件地址的地方,用的还是python2.5,sigh,亏我还在以前的文章里专门写过注意事项。

6. 再次杀死进程,打开网站,nnd,依然是挂的,再次执行t.fcgi,这次报了异常 – flup库没有安装,晕倒(前面步骤2里是全的,当时忘了装了),再次执行,没有啥问题了………中间由于每次fail都会发日志给我的邮箱,还遇上了bluehost发email的限制,不得不在Django配置文件里打开了Debug=True调试选项

7. 再次杀死进程,打开网络,tnnd,依然是挂的;好吧没关系,就放我换个姿势再来一次,在发芽网源文件目录底下,敲入python manage.py runserver,对,就是起了一个本地Django Server,果然起不来,但是可以看到异常了,看出来似乎是PIL里面出的问题,错误信息是MemoryError还提到了ctypes,肿么办?这个吓不倒我,Google之,发现这篇文章,里面说,打开$HOME/lib/python2.7/ctypes/__init__.py,找到CFUNCTYPE(c_int)(lambda: None),注释掉它就ok但是不知道到底为神马。我打开一看,里面居然有一段注释,意思是,写这几行代码的人也不完全清楚为什么要写这行代码:

# XXX for whatever reasons, creating the first instance of a callback
# function is needed for the unittests on Win64 to succeed.  This MAY
# be a compiler bug, since the problem occurs only when _ctypes is
# compiled with the MS SDK compiler.  Or an uninitialized variable?
CFUNCTYPE(c_int)(lambda: None)

8. 这样的神问题都碰上了,我像个小强一样挺了过来,注释掉这一行,居然,就搞定了~~~

9. 终于,发芽网可以打开了,世界和平了,王子和公主从此过上了幸福的生活~~~直到,我发现admin页面无法访问了

10. 这时候我大概已经患上了斯德哥尔摩综合症,看到这个问题,居然虎躯一震,精神焕发,查起了Django1.4的文档,发现,urls.py里面有个地方要改改了。这么快就搞定,很是乏味啊~

11. 终于成功的打开了admin页面,发现页面布局有些古怪,突然想起以前发的另一篇文章Bluehost上架设Django之Admin,原来admin_media还是指向以前安装的1.1的,删掉链接文件重新ln一下,搞定

12. 终于,看起来一切正常了,siteuptime服务告诉我说 back to normal 。。。咚咚呛

13. 然后发现代码发芽网贴代码以后,看起来就是一片漆黑,用chrome的developer tools验证了一下,原来所有的jQuery.ajax调用都会跑到异常分支里去,加上了几行代码发现报错 Unexpected token 。继续Google关键字Unexpected token和jQuery,发现stackoverflow上面有个帖子说这个事情,居然是个jQuery的bug?jQuery会把返回结果当成json进行eval,当然会有些问题啦,于是把所有的ajax调用里面都加上了一句 dataType: ‘text’ 之后,一切ok了

14. 至此,好像一切都好了,第二天,tianyi song同学给我写信,问我为什么代码发芽网不能用了?还给我发来了截图。来回交流了几次,发现我原来的代码里对Pygments的一个格式有严格依赖,在Pygments1.4里面这个格式有些改动,没办法,把Pygments源码里面html.py这个formatter改了几行,总算搞定了

15. 篇幅限制,省略细节问题十数条,还会遇到神马问题?期待~~~

经验总结

1. 不要怕,找到问题,细心分析,想各种方式去debug,搜索Google,大不了去看代码

2. 本地环境和部署的环境的一致性很重要,Service Engineer很重要啊,要想再更新的同时网络不下线,没有SE的支持是很困难的

3. 做个大网站,没有unit test也没有测试团队,那是不可能地~

btw, 或许有人问,搞这么多问题,就为了一个小小的网站,值吗?

值!兴趣是无价的,折腾是永恒的~~~在折腾中学习就是爽~~~!

Python HTTP – Post a Binary File using urllib2

2011年03月30号,星期三 作者:半瓶墨水   链接:http://www.2maomao.com/blog/python-http-post-a-binary-file-using-urllib2/

I was trying to post a png file to our internal webserver for some quick and dirty task, but Python keep throwing ascii encoding exception.

Then I search “post http binary urllib2″ and did found several options, like multipart/form-data and pycurl, but they need the webserver’s help to accomplish a simple file posting.

In the end I decide to dig into Python’s httplib and urllib2 to find out what’s going on, if that doesn’t work I will fallback to raw TCP socket solution.

Luckily I got the following solution that works like a charm:
1. set Content-Length header(of the file) before doing post
2. pass a opened file when doing post

Sample code:

Python语言: Python Post a Binary File using urllib2
import urllib2, os

image_path = "png\\01.png"
url = 'http://xx.oo.com/webserviceapi/postfile/'
length = os.path.getsize(image_path)
png_data = open(image_path, "rb")
request = urllib2.Request(url, data=png_data)
request.add_header('Cache-Control', 'no-cache')
request.add_header('Content-Length', '%d' % length)
request.add_header('Content-Type', 'image/png')
res = urllib2.urlopen(request).read().strip()
return res

贴代码:从命令行卸载程序, Windows XP/Vista/Win7

2011年03月24号,星期四 作者:半瓶墨水   链接:http://www.2maomao.com/blog/uninstall-from-command-line-for-windows/

主要写给自己用,因为天天都跟windows命令行打交道,装了个CCleaner就是因为它比windows自己的“添加/删除程序”要快。最近实现工作中要用到的东西,顺便做得更加通用一点,贴到这里。还挺方便的,因为命令行更快。

保存成u.py,然后如下运行:

D:\Documents\Dropbox\Coding\active>u apple
multiple matches, please choose:

  1  Apple Mobile Device Support
  2  Apple Software Update
  3  Apple Application Support

select a number, Ctrl+C to quit: 3

        Apple Mobile Device Support

press ENTER to uninstall, Ctrl+C to stop
Python语言: Command line uninstaller for Windows Applications
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#Author : Zhongfang Ren
#         yid: renzhongfang
#         email: realfun AT gmail.com
#         website: http://2maomao.com/blog/, http://fayaa.com
#Purpose: Command line uninstaller for windows XP(Vista and Win7 not tried yet)
#Date   : Wednesday, March 23, 2011, 10:42:34
#

import sys, os, getpass
import subprocess as exe
import _winreg as R

target = ''
if len(sys.argv) > 1:
  target = sys.argv[1]

uninstalls = []

for root in (R.HKEY_LOCAL_MACHINE, R.HKEY_CURRENT_USER):
  with R.OpenKey(root, "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") as key:
    keys = []
    i = 0
    while True:
      try:
        keys.append(R.EnumKey(key, i))
        i += 1
      except Exception as e:
        #print e
        break

    for k in keys:
      try:
        with R.OpenKey(key, k) as hkey:
          try:
            v = R.QueryValueEx(hkey, "DisplayName")
            v = str(v[0])
            if v.lower().find(target.lower()) >= 0:
              uninstalls.append((v, R.QueryValueEx(hkey, "UninstallString")[0]))
          except Exception as e:
            #print e
            pass
      except:
        pass

try:
  if not uninstalls:
    print "uninstall for %s not found" % target
  elif len(uninstalls) == 1:
    print
    print "\t%s" % uninstalls[0][0]
    print
    raw_input("press ENTER to uninstall, Ctrl+C to stop")
    exe.call(uninstalls[0][1])
  else:
    print "multiple matches, please choose:\n"
    i = 1
    for nu in uninstalls:
      print "%3d " % i, nu[0]#, '\t', nu[1]
      i += 1
    print
    val = 0
    while val <=0 or val > len(uninstalls):
      try:
        val = int(raw_input("select a number, Ctrl+C to quit: "))
      except ValueError:
        pass
    name, uninst = uninstalls[val-1]
    print
    print "\t%s" % name
    print
    raw_input("press ENTER to uninstall, Ctrl+C to stop")
    exe.call(uninst)
except KeyboardInterrupt:
  pass
finally:
  print