缘起

最近重构了成吨的代码,对于写代码这件事,有了更深的感悟。
看了一些书,《软件随想录》《重构》《Python Cookbook》《程序员修炼之道》《GEB》《SICP》等等,有的书,和重构这件事并没太大关系,但竟然不知不觉自行看出了其中的关联性。

思索

Python是一门优雅的语言吗?是的。
Flask是一个优雅的Web框架吗?是的。
那么,用一门优雅的语言和一个优雅的Web框架,一定能够搭建出来一个优雅的项目吗?不一定。

Python的语法简单,让初学者很容易就可以上手。但写出真正Pythonic的代码,是需要一些经验的。
Python自身的灵活性允许开发者用一些魔法手段实现一些功能,可它没有办法阻止魔法被滥用。
自由度,意味着失控的可能。给菜鸟一门意大利炮,难免会玩不转,甚至炸伤自己。

Django是个好Web框架,虽然很多高手不屑于去用。Django较为固定的项目结构,大而全的内置功能,在有的人看来是种限制。然而从另一个角度来看,正是这种限制,让一个新手,也能够有较为稳定的输出,不会偏离最佳实践太远,更不会因为自身经验不足,或者脑洞大开,而造出一坨难以维护的项目。

用Flask就不一样。灵活,意味着失控的可能。每一个组件,都需要自己去挑选,去考量和项目本身的集成问题,项目的结构,也需要自己去小心维护。其实最后写出来,会发现其实和Django项目也差不太多。可能高手们就是喜欢这种Build it from scratch的感觉?

蟒蛇之禅

Python作为一门强迫症患者的专用编程语言,也难怪要把编程思想塞进标准库里。在一开始学Python的时候,就看过了这首诗,却并不知道他讲的是什么,但随着自身的积累不断增加,也会有更深的理解,这就是禅吧。

如今再次重读Python之禅,原来字字讲的都是如何工程。

Beautiful is better than ugly.
美优于丑。

如果把这首The Zen of Python删得只剩一句,应该留下这第一句话。美胜于丑,字面意思很重要,但深一层的意思更重要。首先,要正确判断什么是美,什么是丑。审美观,其实是一件很私人的事情,人与人之间在审美这件事上,有很大的差别。但Python中却把什么是好味道的代码用了一个词来描述,Pythonic。这也意味着,好的Python代码,风格将是趋于一致的,这也是后面那句“唯一明显的方式完成任务”所说的。

写代码,尤其是写Python代码,从某种意义上来说,和某些设计的工作有点类似。程序员的品位是很重要的。在写代码的时候,保持警觉,时刻注意代码里出现的坏味道。

Explicit is better than implicit.
显优于隐。

Simple is better than complex.
简单优于复杂。

Complex is better than complicated.
复杂优于难以理解。

Flat is better than nested.
扁平优于嵌套。

Sparse is better than dense.
稀疏优于密集。

Readability counts.
可读性最需考量。

写代码,尤其是写业务逻辑代码,最好能够让人一眼看出代码做了什么。有时候,稍微有经验的程序员在这一点时可能还不如新手做得好。因为新手写东西往往不会抽象很多,或用什么高级技巧,写出的代码直白易懂。而大部分人一旦学了一些新东西,就会犯“手里握着锤子,看谁都像钉子”的毛病。找地方练练手总归是好的,但如果要对需要长期维护迭代的生产代码里动手,就要慎之又慎。

与一些库的代码不同,业务逻辑代码,往往需求变更频繁,而且维护这份代码的人员变动也可能很频繁。因此,代码需要简单易懂,分层清晰,且不宜过度抽象。

SICP告诉我们,抽象是一个伟大的想法。通过对高阶过程的调用,可以将过程的细节对调用者隐藏起来,从而提高程序的表达能力。但在实践中我们发现,当抽象过多,且不合理或不必要时,会让代码难以维护。当代码正交性不好的时候,改一处地方,往往会破坏掉其他几处的逻辑。

合理的分层至关重要,每一层都应该用当前层次该用的符号来描述自己的逻辑,界限要清晰,不应该向下跨越多个层次或调用同相同层次或更高层次的逻辑。这样在调试的时候,你就能操作一个层次分明的三明治,而不是纠结成一团的意大利面。

Python自身有很多高级特性,允许开发者借助这些特性,实现一些神奇的功能。但在业务逻辑代码中,这样的黑科技的使用往往让代码变得难以理解。

代码首先是给人读的,只是顺便机器可以运行。执着于黑科技的话,为什么不直接用机器码编程?

Python搭车客指南里有一句话令我印象深刻:

Like a kung fu master, a Pythonista knows how to kill with a single finger, and never to actually do it.
像功夫大师一样,一个Pythonista知道如何用一根手指杀死对方,但从不会那么去做。

知道高端特性很重要,但知道何时该用,何时不该用,才是真功夫。

Special cases aren't special enough to break the rules.
特例不足以破坏规矩。

Although practicality beats purity.
尽管实用性更胜于洁癖。

在任何情况下,都应当遵守编码风格的规则,力求清晰地表达自己的意图,不能为了省事写出一些奇奇怪怪的东西。

Errors should never pass silently.
永远不要默默忽略错误。

Unless explicitly silenced.
除非显式地忽略。

这段讲异常处理。在捕获异常的时候尽可能使用精确的异常类型捕获,而不要用Exception一锅端。不论是自己还是别人,再次读到这份代码时,能更清楚地知道会因为什么原因而抛出异常。和前面说过的一样,显式胜于隐式。能一眼看得懂是在做什么的代码是好代码,一眼看得懂怎样发生了异常、又是如何处理的代码是好的异常处理。

In the face of ambiguity, refuse the temptation to guess.
面对模棱两可之时,拒绝猜测的诱惑。

作为一种动态类型语言,模棱两可之时一定会很多。编码的时候,应当随时注意传入的类型,返回的类型,尽量不要太随意。关键的地方,可以下几个断言,真正出问题的时候,对定位问题有很大帮助。不然过一阵之后,自己都会把自己搞糊涂。Python 3.5开始有了Type Hint,可以用用,就是写起来会啰嗦一点。

There should be one-- and preferably only one --obvious way to do it.
去追寻那个唯一的明显的方式去完成任务

Although that way may not be obvious at first unless you're Dutch.
尽管这个方式在一开始不是那样明显(谁叫你不是Python它爹

与Ruby哲学的显著不同就是这一点。Ruby中做一件事可以有许多种方式,它拥抱多元化。而在Python中,总是去追求那个唯一的、明显的解决方案。但一开始不一定能很快找到那个最佳的方式。不断地编码,思考,总结,经验不断积累,会让那个最佳的路线慢慢明晰起来。

Now is better than never.
做优于不做。

Although never is often better than *right* now.
然而不做却优于立刻去做。

写代码只是工作的一小部分,在这之前的那部分更重要。要多想。尽管程序不像盖楼,盖起来几十年都不变。但之前的准备工作也非常重要。动手之前先看看别人是怎么做的,会有很多收获。写代码前先设计一下,总让自己少走些弯路。

If the implementation is hard to explain, it's a bad idea.
难以解释的实现,是个坏实现。

If the implementation is easy to explain, it may be a good idea.
容易解释的实现,可能是个好实现。

禅没有说,跑得快的实现是个好实现,也没说写得逼格太低的实现是个坏实现。而是有着另一种评价方式:是否容易解释是一个好实现的前提。如果都无法讲请自己的代码做了什么,那么这份代码要么逻辑混乱,连作者自己都不清楚是怎样工作的,要么太过复杂,难以日后接手维护。不论从哪方面来说,不容易解释的代码,一定不是好代码。

Namespaces are one honking great idea -- let's do more of those!
命名空间是个好东西,应该多用用。

安利了一下命名空间。确实值得一用。

工程

归根结底,工程不只是编程问题,而是与整个软件的生命周期息息相关。大学里有一门课叫软件工程,虽然我们都知道,这门学问很重要,但教材上写的,似乎与现实有很大脱节,难以应用到实际中。

然而,做一个工程的本质其实还是没变的。需求分析,设计,编码,测试,交付,维护。在工程中,就不是理想环境,所以每个环节能够投入多少,期望产出多少,处处都是权衡与妥协。

很不喜欢那个把编写程序与盖房子相提并论的类比。一栋楼改好之后,它的整体功能和结构,就很难出现太大的变化。传统的软件工程也是这个思路。

但软件,是活的。贯穿于整个生命周期的,是变化。

在它的生存周期里,将不断的因为需求的变化而生长、进化,也会有不需要的功能退化掉。因为周围环境和用户需求变化而不断演变,这是一个有生命力的软件的基本特性之一。而有时候,功能不变,而内部构造产生颠覆式的改变,这就是重构。重构不是为了增加新功能,而是为了更加容易地增加新功能。毕竟,磨刀不误砍柴工。

写代码,就是与计算机对话,与程序对话,与复杂度对话,与自己对话。一遍遍打磨代码,一遍遍打磨自己的思维。

也许这就是禅。