The perfect is the enemy of the good. The best is the enemy of the good. – Voltaire
Perfectionism kills me.
you are coming a long way…
The perfect is the enemy of the good. The best is the enemy of the good. – Voltaire
Perfectionism kills me.
在《Clean Code》中提到了函数传递的参数不应该超过3个,如果超过三个推荐将它们变为符合数据结构。
在Python的应用中,对于这个场景,我看到一般的做法就是使用Hash。其实Hash就是一种Key-Value store,是一种弱类型的结构,好比一个没有shema的object。这样做起初看来是很舒服的,因为它自然的让你可以访问到一个复合数据结构。可是据我观察这一般都是Bad smell。缺点在于:
对于以数据为中心的系统,你看到一个Hash被传递于超过一次函数调用,那么你最好对它马上进行重购。将它处理为一个可控的数据类型(如Class和Namedtuple)。
非受控类型还有另外一种表现形式,那就是tuple。tuple让python变得强大,尤其是它被用在平行赋值的情况下,如:
def fucntion_a(): a, b = 10, 20 return a, b def function_b(): price, amount = function_a()
这种情况下它有很不错的表现力。但是在非平行赋值的情况下使用tuple传递数据结构就是一种Bad smell了。如:
def function_a(): my_tuple = ('tin', 'male', 28) return my_tuple def function_b(): a_tuple = function_a() name = a_tuple[0] age = a_tuple[2] .....
这种情况的邪恶是使用了顺序来约定数据结构,它比起hash来说明显的问题就是僵化、不容易阅读。僵化是说当你发现需要传递更多数据的时候你需要在顺序中添加新的元素,此时如果你像插入在前面几个元素之间,你会面临大量的index修改操作,非常容易造成bug。不容易阅读是,如果不在同一个文件中,你怎么知道0, 1, 2分别是哪个属性的index,为此你肯定花大量力气写注释,而且这个注释需要随你的修改而修改,非常恼人,遗漏了就是bug。所以它们那是明显的bad smell。
上面两种情况属于同样一种情况,那就是使用非受控数据结构表示结构化数据。那么如何“使用受控类型而不是Hash和tuple”呢?一般来说很简单。一种是写一个数据结构的Class,这种情况是你发现这里的数据结构是潜在的领域模型的时候非常有用:
class BusinessLogData: date = datetime.now() username = 'Unknow user' business_type = '' .....
好处是当你发现系统中围绕这个数据结构的算法或者说行为的时候你马上就有一个地方存放这些逻辑了,会让你很舒服。而且逐渐的你的领域模型就会清晰了,这对没有使用领域模型驱动的系统进行重构的时候很有用。另外一种情况,你可能知道没有什么逻辑在数据结构上,它就是给算法访问的纯数据type。那么可以使用namedtuple。它实际上是一种元编程。官方的例子是这样的。
Point = namedtuple('Point', 'x y', verbose=True) p1 = Point(11, y=22) p2 = Point._make((11, 22))
我个人认为namedtuple非常方便,很象ruby中的struct。它对于已经使用tuple传递数据的系统的重构非常有效,因为_make这样使用tuple创建namedtuple实例的方法非常适合重构,可以马上消除0, 1, 2这样的index magic number。而且在使用sql的时候它也是非常有用的工具,官方例子:
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade') import csv for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))): print emp.name, emp.title import sqlite3 conn = sqlite3.connect('/companydata') cursor = conn.cursor() cursor.execute('SELECT name, age, title, department, paygrade FROM employees') for emp in map(EmployeeRecord._make, cursor.fetchall()): print emp.name, emp.title
OK,到这里这个重构实践就阐述完毕了。下面我会继续讨论Refactory in Python这个话题。
我问他最近在研究什么,他说他最近在研究安全。我问他安全是指哪方面的安全,他说主要是跨站攻击的相关标准。他说网站的安全主要是HTML标准的落后造成的,所以这里非常混乱,总有各种棘手的跨站攻击问题。要想解决跨站攻击的问题,一定要有标准来规范跨domain的通讯,来保证其安全。
关于Lucas Film,他说那段时间非常棒!他负责技术的研究,他举例说CD啦、DVD啦还有各种他们特效相关的一些技术。
我问了他为什么的新版本为什么没有人推广,反观HTML就有很多人在推广。
关于Html,Corkford说这里根本就没有一个标准,已经很久都没有一个标准发布了。他说HTML 5只是一个proposal,它不是一个标准。我问他真正在推广HTML 5的大公司有哪些呢,他说只有Google在真正的推广它。
我问他对服务器端使用Javascript怎么样?他说这是Great的,而且能看出他对此非常高兴。我问他是否在使用Node.js,他说Yahoo已经在尝试使用Node.js,其中包括用它来帮助测试YUI3。我问他现在Node.js的api变化似乎还比较大,他说是这样的,因为它正在被活跃的开发中,他说非常期待他尽快成熟并被更加广泛的使用。
我问他对CSS3现状的看法。他说现在CSS也是很久没有一个标准了,而且先前的CSS标准设计的非常不好,没有解决Web开发者遇到的问题,给他们带来了很多的痛苦。我问他对CSS3的看法,他说现在的路线走的不对,它没有很好的去解决关键问题。他认为CSS现在需要解决关键的问题是对于不同的显示设备的排版的支持。他说现在大的显示器,还有更细密的显示器,还有小的显示设备,他们对复杂排版的支持的特性的考虑都太差了。所以我们就无法用css实现让你的排版/布局在各种设备上看起来都是”Great”。
我问他对于这些不同设计的显示设备的布局是不是有两个瓶颈,一个是设计师本身的瓶颈,另外一个是css特性的瓶颈。他说不是的,他说设计师是没有瓶颈的,问题都处在css标准对这些多列的布局的描述太欠缺了。我问他从Ux的角度看人类的阅读不能很宽,但是似乎CSS3已经开始想要解决这个问题了。他说现在的解决方案还很不够,因为它们都没有从阅读和设计者的角度去考虑排版,所以它就无法让你的Web页面在手机上看起来是单列布局,而到了巨大的显示器上的时候可以像报纸一样使用阅读舒适的多列布局。他认为是这个问题限制了设计师的创造力,他认为在合理的css特性下,设计师可以完成很完美的自适应布局设计。
我问他对于Jim Webber所说的Web as platform的看法,他说Web是一个非常棒的东西,但是Web标准是非常烂的。因为现在大家越来越认识到Web的强大好用的时候,标准已经成了万恶之渊(这个词是我杜撰的),所以现在大家要花更大的力量在Web标准上。他说现在Web标准的一个很大问题是模块化不够好,或者说切分的不够细。造成超级多的特性被堆积在一个标准里面,这样这个标准就一直无法发布,并且也会越来越难用。他认为应该按照我们的使用场合和特性将Web标准切分开,帮助每一个部分可以关注一个点,这样的Web标准发布的就会频繁很多。解决我们现在面临的大量问题。
我问了他如何做Javascript的测试,他说的确很烦人,说Javascript的一个麻烦的问题就是无法在服务器端测试。我问他Yahoo是否也就是使用Selenium这样的客户端测试工具,他说实际上他们也是使用这样的工具。他说这里的问题还是在于标准,因为你做客户端测试实际上不是在测试你的代码,而是在测试浏览器。他说因为浏览器对标准实现各异,而且他们还有自己的bug,造成你实际上是在这些浏览器写测试。他说如果标准能够更好,这里就会减少痛苦。
我问他你认为在View这一层是否应该分开给人看的View和给计算机读的View,这里是否有一种中间的View能够结合它们呢?他说这不就是Json么?我问他Json对于人类来说易读么?他反问我他不是已经比XML容易读了很多么?他说Json已经是一种非常接近于人类自然的描述数据的方式了。(而Html还是倾向于为人类的阅读而设计的数据结构,当然他是面向浏览器的。)
他说今晚他要讲的话题就是关于我们谈的这些问题的,关于标准,关于Web。
Aiming for the wrong target
Assumption $1
Users care about the things the do-features-not the software or hardware you run
Assumption #2: Faults and errors will occur.
Your can choose to engineer safe failure modes into your system or to accept whatever random failure modes naturally occur
Engineering Failure Modes
Tolerance : Absorb shocks . but do not transmit them
Severability: Limit functionality instead of crashing completely
Recoverablility: Allow component-level restarts instead of rebooting the world
Resilience: Recover from transient effects automatically
These produce consistent availability of reatures
工程化失败模式
容忍:将震荡吸收,而不是传递它
服务能力:功能缩水而不是整个损坏
恢复能力:允许组件重启,而不是让整个世界“重新启动”
弹性:能够从瞬时性(Transient)的影响中恢复
这样可以保持功能的可用性
Stability Antipatterns
1. Intergration Poinnts
Intergrations are the #1 risk to stability
Your first job is to protect against integration points
Every socket process. Pip or remote procedure call can and will eventlually kill your system
Even database calls can hang. in obvious and not-so-obvious ways
“In Spec” vs. “Out of Spec”
“In Spec” failures
TCP connection refused
HTTP response code 500
Error message in XML response
Out of spec failures
TCP connection
Remember this
Large systems fail faster than small ones
2. Chain Reaction:
Cascading Failure: Failure in one system causes calling systems to be jeopardized
Remember this
Prevent Cascading Failure to stop cracks from jumping to the gap
3. Users: Can’t live with them…
First type of “bad” user
Front-page viewer: creates useless sessions, ties up memory for no reason
Application servers are all fragile to sessions: Users can ….
Handle traffic surges gracefully: Turn off expensive features when the system is busy. Divert of throttle users. Preserve a good experience for some when you can’t server all. Reduce the burden of serving each user. Be especially …
Second type of “bad” user
Buyers: (most expensive type of user to service, secure pages requires more cpu, more pages, external integration), High conversion rate is bad for the systems!. Your sponsors may not agree
Blocked Threads: Request handling threads are precious. Protect them.
Most common for of “crash”: all request threads blocked. Very difficult to test for:. Best bet: keep threads isolated. Use well-tested. High-lvel contracts for cross-thread communication
Attacks of Self-Denail: Good marketing can kill your system at any time
Defending the ramparts: avoid deep links, setup static landing pages, only allow the user’s second click to reach application servers. Allow throtting of …
Remember this
Keep lines of communication open , protect shared resources, expect ..
Scaling Effects
*QA and Dev balance?
Unbalanced Capacities
Traffic floods sometimes start inside the data center walls.
SLA Inversion: Surviving by luck alone.
Unbounded Result Sets: Limited resources, unlimited data volumns
记住:要使用显示的数据容量测试
每次用Tunnelblick连接我的zztin.com主机后(我的是Mac OSX 10.6)再断开后,我的zztin.com的主机就无法到达了。ping的时候会报告:
ping: sendto: No route to host
这是路由错误的表现,用netstat -nr查看当前的路由设置,发现连接OpenVPN时候推送的route信息没有被清除。我手动试验了一下重建route表:
sudo route flush
然后所有网都连不上了,又netstat -nr一下,发现默认路由没有了,手动添加一下(我家的无线路由器IP是192.168.1.1:
sudo route add 0.0.0.0 192.168.1.1
所有网站都可以用了,包括ssh我的zztin.com。可是不能每次都手动清理呀。查看了一下log,发现的确有问题,有好几行route -delete的时候报错:
ERROR: OS X route delete command failed: external program exited with error status: 77
搜索了一下,发现需要注释掉我的openvpn客户端配置里面的:
# user nobody # group nobody
然后一切都OK了。
配合O’reilly老爷爷的一些想法,我也想想我共鸣的想法:
这只是第一部分,时间不够了,待续……
上上周导入数据,我们约定了数据格式,写好了导入脚本,进行了充分的测试。但是当天给我们的数据结构却发生了巨大的变化,但是给我们的更新window就是当天,所以我们只能再写程序将格式转化为我们先前约定的格式。我和 @nasiless 同学结对做这件事。经过3、4个小时的努力,转换没问题了,将中间数据转化的操作演练了一下,没啥问题。当准备好停机升级,又从数据提供方得知其中一部分数据是错误的,然后又给我我们一份补丁文件(名-值对,需要覆盖现有值)。我们由于担心重新生成数据非常费时间,所以分析一下补丁对我们操作的影响。@nasiless 同学和我确认应该只影响其中一个中间数据文件,然后写脚本修正了一下。最后我们“成功”导入数据恢复服务了,当时很庆幸居然成功了(那个时候已经是晚上11点了)。当然其中有一部分导入失败的数据(9xx条)我们输出了一份文件准备后面手工输入。
一周后来(期间系统又进来很多新数据),我们发现导入的数据中失败的那部分可能是由于补丁文件也需要应用在另一部分的中间文件上造成的。可是当我们想反推这个过程并重现它的时候,我们发现我们的临时转换脚本(就是把那天修改的数据文件向中间文件转换的程序)居然放在了我的/tmp目录下,而其间我的mac重启过,所以这些脚本已经不见了。剩下的只有那个导入失败的log和映射补丁文件,我们经过推演问题已经变得非常复杂了。几个错误因素结合在一起,数据再回复的确是个非常费脑子的工作。还好经过反复排查,这些结果都是可逆的,不过其中人工操作众多,非常费事。我们也只能硬着头皮做了。因为这个系统还涉及到钱的问题,钱又涉及分帐问题,所以反推非常麻烦。
我们的结果还算好的,因为有方法反退效果。可是最大的问题在于这个过程本身应该是“事务”,必须全部完成或者全部恢复,当时我们的做法没有遵循这个基本的原则。而且我们的导入程序中复用了系统逻辑代码,但是忘记修改事务边界,没有将多个事务join起来,所以造成了脏数据的出现。这个也是后来折腾我们的一个主要原因。
所以教训就是:数据导入操作本身就应该使用事务,确定好所有的事务边界。而且数据导入的流程也应该是一个事务,不应该允许可能出现脏数据的请款存在,因为这种问题往往是追悔莫及的,也就是safe steps的极致。
周六才到手的G1险些在今天变砖,历程是这样的。内容不专业,不是帮您刷机的,只是告诉您怎样刷坏的。
中午吃饭下楼正巧碰到Andriod牛人“小新(quakelee@twitter)”,吃饭间请他一起研究我新买的G1。小新本人用的也是G1,旁边几位同事也不少G1,刷机大都是由小新帮忙。而小新也欣然接受帮我刷机。
吃饭归来就去我们的“不拆不舒服司机”和“不焊不舒服司机”的小魔屋(SA办公室)刷机。小新看了下我的基本情况,是TIM的西班牙版本机(或者是意大利版),当时的主Rom是hiapk的2.2版本,里面有SPL能够启动Fastboot,副rom不明。
尾声。小新告诉我原来是大胖同学解救了我俩。这个机器按着照相快门键启动就可以引导入SPL,然后就可以刷机,刷机后就好了!所以是大胖救了我们。原因是小新刷的机器都是按return和开机进入SPL,但是大胖的机器按快门和开机进入。所以按照大胖的习惯我的机器就开开了。后来测试发现,我的机器只支持快门+开机进入SPL,而小新的机器只支持return+开机进入SPL,而大胖的机器两个快捷键都可以。所以世界还真是无奇不有。
老婆到达的时候我的G1已经又工作正常了,不过心灵真的受到了打击。一个到手3小时的新G1,居然马上就砖了,本来为了省钱买它,如果再入手一个那还真要用上一个iPhone 3G的价格了,吓人呀。不过还好,修好了,这1850没有打水漂。一个教训就是,下次做这样心跳的事情要多做功课,每一步想好撤退方案再行动,所有危险动作前要double check前置条件是否达到,否则,那还真是一步一步走向“变砖”,连头你都没法回了。
感谢帮助刷机3个半小时的小新,感谢帮助G1起死回生的大胖,感谢资深技术支持蛋总。感谢团队的围观群众舜佳、彭一、远超,还感谢老婆没有责怪我。
圣诞夜和老婆去逛三里屯,着重逛了一下顺电。体验一下手机。
一直犹豫是否应该选择iPhone 3G或者iPhone 3Gs,其实大部分的YY都是在我的iPod touch上完成的,所以我非常希望这个用户体验流畅的开放平台。几天前实验一位同事的3Gs的时候还觉得它是我的终极选择。可是今天在顺电又实际体验了一下发现它与iPod touch的差异部分,也就是电话部分并不好用。比如短信界面乍看来不错,可是中文比较短,所以在精美的气泡布局上面显得很奇怪,不好看。与Android手机或者任何同价位的手机比起来,iPhone的屏幕也不显得精细了。而通讯录,不好不坏,与Android上面的通讯录无法来开档次。从价格上面来说,我觉得有点贵了,它已经比同样硬件平台的手机贵接近一半了。所以我准备暂时打消iPhone的期盼,期待联通带wifi版的补贴iPhone出现了。
接下来实验了一下G3,比较认真的体验了短信及输入法的体验。感觉软键盘对于这样一个手机还是不舒服,失去了很多输入的快感。相比之下今天白天实验超哥的G1,里面的硬键盘配合触摸屏选择文字很舒服。而Google maps和Gtalk也是非常的亲切,它们在无限通讯上会给我很大的安慰。我想实时网络应用,如Twitter和Gtalk估计是我现在对于G1/G2/G3最大的期待了。G1/G3的屏幕我非常喜欢,细腻明亮。键盘、屏幕、原生app与google的高度集成目前让G1成为我最希望入手的手机。
最后剩下黑莓(8820/8310),其实按理说它是和G1一样价位的手机,可是号称十四天机和打孔机的出现让它跌入1k价位,这个让我有点不放心。再说黑莓不是个开放平台,下午学虎也跟我说因此少了很多DIY的乐趣,它似乎更适合希望省心的偏商务的人士。而且黑莓的屏幕是横向的,体积比较小,看起来不是很舒服。而且黑莓的字体似乎是我非常不喜欢的“宋体”,我更喜欢平滑的“黑体”,这也是一个让我不爽的地方。
所以,我想,周末如果允许的话,我和老婆想去中关村现场实验一下黑莓和G1,然后结束这场犹豫的选择游戏。
在讯奇,身边一帮魔头,都是咸人,对我带来做Daily standup的小熊天天进行蹂躏。今天留下一图,其实这不算被虐待严重的情况,有很多情况下它就是个球: