程序员的核心能力

  • “图片提交之前能不能用 TinyPNG 压缩一下”

  • “data.py 是数据文件,不需要运行单元测试”

  • “我们另一个 git repo 也想用你的 precommit 脚本,但要把 Python 的单元测试先禁掉”

  • 1), 2)都是小意思,加上就行了。3)让你有点为难,precommit 函数已经有点长了,要不这个判断写在 py_unittest 里吧。到了4),你忍不住顶了回去“我没空写,拷贝一份自己去改吧!”

    这时你开始意识到这样下去不是办法,但别人的这些需求又很琐碎,让你没办法很好地组织代码。如果要系统化地解决这些问题,该怎么做呢?你发现所有的需求都可以抽象成同一个套路

    如果文件名符合某个条件,就对该文件执行一个特定函数

    如果让使用者来提供文件名的条件以及要执行的函数,那么你只需要构建一个引擎来做条件判断,并为要执行的函数准备参数就行了。有了这个思路后,你把每个需求抽象成了一条规则,规则包含以下几个属性

    • id: 规则的名字

    • include: 适合的文件名

    • exclude: 要剔除的文件名

    • func: 对符合条件的文件,要执行的函数

    • warning_only: 如果设为 True,在 func 执行失败时,只打印警告。否则,中断提交。默认为 False

    你把代码重构成了下面这个样子

    rules = [ { 'id': 'pylint', 'include': ['*.py'], 'func': pylint, 'warning_only': True }, { 'id': 'py-unittest', 'include': ['*.py'], 'exclude': ['data.py'], 'func': py_unittest }, ...]def precommit(): files = get_commit_files() run_hooks(files, rules)

    run_hooks 是你整个引擎的入口。而那些具体的需求不再是引擎的一部分,而只是外部输入。更进一步,你把 rules 放在一个单独的 JSON 或是 YAML 文件中。这样别的团队可以定制自己的 rules 配置文件,然后直接使用你的 precommit 引擎,这样就完美解决了之前的第4个需求。

    这里不给出 run_hooks 的具体实现,有兴趣的同学可以自己写写看。

    避免过度设计

    当你有了引擎思维之后,也要小心另一个极端,那就是过度设计。这里有两种情况,一种是过早地设计引擎,没有从实际的需求中去总结抽象,而是凭自己的臆测。对于一个了解业务的资深工程师来说,这样做也许是可行的,但如果你经验不足,还是别太相信你的预判。另一种过度设计是想让引擎能满足所有的需求,这可能让你的引擎变得复杂而难用。在设计引擎时,我也会使用80/20原则,让引擎能很好地解决80%的需求,对于剩下的20%,引擎需要有一种 fallback 机制,它只要做到不挡道,能让使用者自己去解决这些需求就行了。过度设计可以聊的点很多,将来可以单独发文讨论,这里就此打住。

    培养引擎式思维

    我是在工作几年之后,才逐渐有引擎式思维的意识。对于每个待解决的问题,开始问自己“它是不是某类问题中的一个个例,这类问题能否被系统化地解决”,而随着经验地增长,越来越多的时候我会得出肯定的答案。因为这是一个长时间累积的过程,我鼓励大家尽早地思考这个问题,对你的技术进步一定会有帮助的。

    从另一个角度,如果你已经是团队中的技术领导,在与团队成员的技术讨论中,在 Code Review 时,你该教他们些什么?Coding style, 某种语言的语法糖,一个可以解决他们手头问题的第三方库?这些是必要的,但还不够。初出茅庐的小伙伴们可能为了完成布置的任务就已经疲于奔命了,或是沉醉在某项新技术的学习中,但他们未必会有意识地去思考如何搭建一个引擎来解决一类问题。在这方面,初期他们需要有好的范例去模仿,中期需要有人提醒指引,日久之后他们才会自发地造引擎,在中前期一个优秀的技术领导若给于他们这方面的帮助,就能大大加速他们核心能力的成长。我觉得这是培养团队成员的重要一环。


    分享到:


    相關文章: