跳转至

09 粗放与精益:编程的两种思路与方式

几年前,我给团队负责的整个系统写过一些公共库,有一次同事发现这个库里存在一个Bug,并告诉了我出错的现象。然后我便去修复这个Bug,最终只修改了一行代码,但发现一上午就这么过去了。

一上午只修复了一个Bug,而且只改了一行代码,到底发生了什么?时间都去哪里了?以前觉得自己写代码很快,怎么后来越来越慢了?我认真地思考了这个问题,开始认识到我的编程方式和习惯在那几年已经慢慢发生了变化,形成了明显的两个阶段的转变。这两个阶段是:

  • 写得粗放,写得多
  • 写得精益,写得好

多与粗放

粗放,在软件开发这个年轻的行业里其实没有确切的定义,但在传统行业中确实存在相近的关于 “粗放经营” 的概念可类比。引用其百科词条定义如下:

粗放经营(Extensive Management),泛指技术和管理水平不高,生产要素利用效率低,产品粗制滥造,物质和劳动消耗高的生产经营方式。

若把上面这段话里面的 “经营” 二字改成 “编程”,就很明确地道出了我想表达的粗放式编程的含义。

一个典型的粗放式编程场景大概是这样的:需求到开发手上后,开始编码,编码完成,人肉测试,没问题后快速发布到线上,然后进入下一个迭代。

我早期参与的大量项目过程都与此类似,不停地重复接需求,快速开发,发布上线。在这个过程中,我只是在不停地堆砌功能代码,每天产出的代码量不算少,但感觉都很类似,也很粗糙。这样的过程持续了挺长一个阶段,一度让我怀疑:这样大量而粗放地写代码到底有什么作用和意义?

后来读到一个故事,我逐渐明白这个阶段是必要的,它因人、因环境而异,或长或短。而那个给我启发的故事,是这样的。

有一个陶艺老师在第一堂课上说,他会把班上学生分成两组,一组的成绩将会以最终完成的陶器作品数量来评定;而另一组,则会以最终完成的陶器品质来评定。

在交作业的时候,一个很有趣的现象出现了:“数量” 组如预期一般拿出了很多作品,但出乎意料的是质量最好的作品也全部是由 “数量” 组制作出来的。

按 “数量” 组的评定标准,他们似乎应该忙于粗制滥造大量的陶器呀。但实际情况是他们每做出一个垃圾作品,都会吸取上一次制作的错误教训,然后在做下一个作品时得到改进。

而 “品质” 组一开始就追求完美的作品,他们花费了大量的时间从理论上不断论证如何才能做出一个完美的作品,而到了最后拿出来的东西,似乎只是一堆建立在宏大理论上的陶土。

读完这个故事,我陷入了沉思,感觉故事里的制作陶器和编程提升之路是如此类似。很显然,“品质” 组的同学一开始就在追求理想上的 “好与精益” ,而 “数量” 组同学的完成方式则似我早期堆砌代码时的“多与粗放”,但他们正是通过做得多,不断尝试,快速迭代 ,最后取得到了更好的结果。

庆幸的是,我在初学编程时,就是在不断通过编程训练来解答一个又一个书本上得来的困惑;后来工作时,则是在不断写程序来解决一个又一个工作中遇到的问题。看到书上探讨各种优雅的代码之道、编程的艺术哲学,那时的我也完全不知道该如何通往这座编程的 “圣杯”,只能看着自己写出的蹩脚代码,然后继续不断重复去制作下一个丑陋的 “陶器”,不断尝试,不断精进和进阶。

《黑客与画家》书里说:“编程和画画近乎异曲同工。”所以,你看那些成名画家的作品,如果按时间顺序来排列展示,你会发现每幅画所用的技巧,都是建立在上一幅作品学到的东西之上;如果某幅作品特别出众,你往往也能在更早期的作品中找到类似的版本。而编程的精进过程也是类似的。

总之,这些故事和经历都印证了一个道理:在通往 “更好” 的路上,总会经过 “更多” 这条路。

好与精益

精益,也是借鉴自传统行业里的一个类比:精益生产。

精益生产(Lean Production),简言之,就是一种以满足用户需求为目标、力求降低成本、提高产品的质量、不断创新的资源节约型生产方式。

若将定义中的 “生产” 二字换成 “编程”,也就道出了精益编程的内涵。它有几个关键点:质量、成本与效率。但要注意:在编程路上,如果一开始就像 “品质” 组同学那样去追求完美,也许你就会被定义 “完美” 的品质所绊住,而忽视了制作的成本与效率。

因为编程的难点是,无论你在开始动手编程时看过多少有关编程理论、方法、哲学与艺术的书,一开始你还是无法领悟到什么是编程的正确方法,以及什么是“完美” 的程序。毕竟纸上得来终觉浅,绝知此事要躬行。

曾经,还在学校学习编程时,有一次老师布置了一个期中课程设计,我很快完成了这个课程设计中的编程作业。而另一位同学,刚刚看完了那本经典的《设计模式》书。

他尝试用书里学到的新概念来设计这个编程作业,并且又用 UML 画了一大堆交互和类图,去推导设计的完美与优雅。然后兴致勃勃向我(因为我刚好坐在他旁边)讲解他的完美设计,我若有所悟,觉得里面确实有值得我借鉴的地方,就准备吸收一些我能听明白的东西,重构一遍已经写好的作业程序。

后来,这位同学在动手实现他的完美设计时,发现程序越写越复杂,交作业的时间已经不够了,只好借用我的不完美的第一版代码改改凑合交了。而我在这第一版代码基础上,又按领悟到的正确思路重构了一次、改进了一番后交了作业。

所以,别被所谓 “完美“ 的程序所困扰,只管先去盯住你要用编程解决的问题,把问题解决,把任务完成。

编程,其实一开始哪有什么完美,只有不断变得更好。

工作后,我做了大量的项目,发现这些项目都有很多类似之处。每次,即使项目上线后,我也必然重构项目代码,提取其中可复用的代码,然后在下一个项目中使用。循环往复,一直干了七八年。每次提炼重构,都是一次从 “更多” 走向 “更好” 的过程。我想,很多程序员都有类似的经历吧?

回到开头修改Bug 的例子,我用半天的时间改一个Bug,感觉效率不算高,这符合精益编程的思路吗?先来回顾下这半天改这个Bug 的过程。

由于出问题的那个公共库是我接到Bug 时的半年前开发的,所以发现那个Bug 后,我花了一些时间来回忆整个公共库的代码结构设计。然后我研究了一下,发现其出现的场景比较罕见,要不不至于线上运行了很久也没人发现,属于重要但不紧急。

因此,我没有立刻着手去修改代码,而是先在公共库的单元测试集中新写了一组单元测试案例。单元测试构建了该Bug的重现场景,并顺利让单元测试运行失败了,之后我再开始去修改代码,并找到了出问题的那一行,修改后重新运行了单元测试集,并顺利看见了测试通过的绿色进度条。

而作为一个公共库,修改完成后我还要为本次修改更新发布版本,编写对应的文档,并上传到 Maven 仓库中,才算完成。回想这一系列的步骤,我发现时间主要花在了构建重现Bug 的测试案例场景中,有时为了构建一个测试场景编写代码的难度可能比开发功能本身更困难。

为修改一个Bug 付出的额外单元测试时间成本,算一种浪费吗?虽说这确实提高了代码的修复成本,但也带来了程序质量的提升。按前面精益的定义,这似乎是矛盾的,但其实更是一种权衡与取舍。

就是在这样的过程与反复中,我渐渐形成了属于自己的编程价值观:世上没有完美的解决方案,任何方案总是有这样或那样一些因子可以优化。一些方案可能面临的权衡取舍会少些,而另一些方案则会更纠结一些,但最终都要做取舍。

以上,也说明了一个道理:好不是完美,好是一个过程,一个不断精益化的过程。

编程,当写得足够多了,也足够好了,你才可能自如地在 “多” 与 “好” 之间做出平衡。

编程的背后是交付程序系统,交付关心的是三点:功能多少,质量好坏,效率快慢。真实的编程环境下, 你需要在三者间取得平衡,哪些部分可能是多而粗放的交付,哪些部分是好而精益的完成,同时还要考虑效率快慢(时间)的需求。

编程路上,“粗放的多” 是 “精益的好和快” 的前提,而好和快则是你的取舍:是追求好的极致,还是快的极致,或者二者的平衡?

在多而粗放和好而精益之间,现在你处在哪个阶段了?欢迎留言谈谈你的看法。

精选留言(15)
  • 👍(35) 💬(1)

    我感觉自己处在一个由粗到细的转换过程,回想我们的代码产生过程,大概如下: 1:prd评审 2:表结构设计、缓存结构设计、程序逻辑梳理,输出对应的文档,进行初评 3:代码编写 4:自测、提测 5:代码review 6:调整和完善代码 7:合并主干回归测试 8:上线准备、上线申请、上线、产品及业务线上验证 9:线上观察 其中第5步,资深和架构都会参与,一般都会提出一些改进和完善意见。这个环节可看出编码功力的深浅及考虑的是否全面。 主要会考察一下方面: 1:业务逻辑是否OK 2:是否打点 3:日志是否OK 4:代码性能是否良好 5:基本的编码规范是否符合 6:代码可读性如何 7:异常处理的是否合理 刚开始总会拉下些什么,现在这个模式已经比较熟练了,不过还是会有一些改进意见,比如:应用交互是否OK、代码抽象层级是否合理 非常感谢我们的代码review同学们,让我成长不少。

    2018-08-22

  • third 👍(10) 💬(1)

    1.个人觉得粗放并不是多,核心在“迭代”,这是一个慢慢变好的过程 迭代的好处就是试错和反馈。 试错,是快速找到自己根本没有意识到的错误,看一遍和做一遍差距太大 反馈,你会在做不停做中得到反馈,并迭代 2.快速是其中关键因素,迭代速度越高,成长也越快 3.粗放和精细,背后其实是成本 比如文中老师和老师同学的例子。老师做了改进,吸收到了《设计模式》的一些知识,很大程度上,是因为老师开始做这件事情的成本其实比那个刚开始做的老师同学低得多。 毕竟,当时的老师已经有一个程序了。 而老师的同学在一个零基础上做程序,其实成本更高。 4.个人想法 理解我们在当下得到的一切,都是此前一切总和结果中,最好的一个。 粗放也好,精益也好。 其实我们的能力都是一样的 变得精益也许只是一个结果,成本更低。 要写更优雅的代码,更好的运行一切的一切,都是因为越来越复杂的代码和框架逻辑,更加大型的用户使用量等等硬性制约而已。 慢慢成长就好

    2018-08-22

  • 依米 👍(8) 💬(1)

    老师,我也是发现自己编码速度越来越慢,不过不是老师的情况,大约是和你的同学类似,越来越不敢下手写代码了,写之前要纠结很久,各种考虑,很不喜欢现在的样子,但还是会不自觉的沉浸在里面,该咋办?

    2018-11-14

  • 艾尔欧唯伊 👍(6) 💬(1)

    粗方式才能有快速产出,产出积累到一定量之后,精益才能提取精华,浓缩沉淀。

    2018-08-22

  • KaitoShy 👍(5) 💬(1)

    在没有成熟方案时,以快速实现为主,而有了之后再慢工出细活?

    2018-08-22

  • 阿信 👍(3) 💬(1)

    介绍编程的两种思路,完美型和现实型。 看这篇文章,脑海中想到了两件事情,一是卖油翁的故事;二是态度(吴军老师写的)一书中提到的一些观点:最好是更好的敌人(或者说进步一点比什么都不做好)、做事情时境界要高。 陶器制作第一组的同学,可以说是熟能生巧;陶器制作第二组的同学以及课程设计的同学,境界是比较高的,但在其事情落地上执行方式有点问题,因为没有找到完美的方案而踌躇不前,最终没有输出满意的成果。 套用到我们自身的工作,以及绝大多数程序员身上,大量的开发(+用心理解)可以提升我们的水平,我们要求输出的成果有deadline,设计时我们格局可以大一些,考虑后来的扩展,实现时可以分步来执行,多次的改进让我们朝着目标前进,甚至超过原有心中的完美方案。

    2019-03-10

  • xhavit 👍(3) 💬(1)

    在质量、成本和效率之间做好权衡和取舍,先完成,后完美,嗯,改掉这个阶段的完美主义思维

    2018-11-26

  • 朱月俊 👍(3) 💬(1)

    我现在编码状态是:务必要在可能出异常的代码附近加日志;一定要测试程序的性能是否符合要求;编码风格务必符合规范;参照同类开源代码参考编码方式。

    2018-10-17

  • 八戒 👍(2) 💬(1)

    老师,最近在处理问题的时候,总是觉得项目以前的代码有问题,冗余不够精简,喜欢去提取公共模块封装再处理,尝试着怎么把代码质量提上去,不知道这是不是就是您说的精益阶段呢

    2018-09-03

  • Kuzaman 👍(2) 💬(1)

    本章收获颇多,我是做测试的,甚至不是测试开发级别,只是偶尔写写代码。目前只能处于粗放阶段,我一直困惑于精细还是粗矿,买了很多书很多视频看,总想着精细化,后来代码一直没有产出。听了老师的课,真是答疑解惑,因此决定先出量粗矿着到一定程度再说。感谢!

    2018-08-25

  • 偏偏喜欢你 👍(2) 💬(1)

    老师您好,我现在正处于粗放的阶段,都是以完成需求为目的,暂时还没考虑到你所说的精益。

    2018-08-22

  • J. 👍(1) 💬(1)

    一路看下来,我觉得老师说的东西很多都是相见恨晚,我作为一个内向的人苦于自己的闭塞走了很多的弯路,完美主义等问题都是至今困扰我的问题,要是早点看到这些资料应该会少走很多弯路吧。

    2021-09-28

  • 苦行僧 👍(1) 💬(1)

    小到一个类名 一个函数名 一个变量名 一段超过几百行的类 都值得再次优化改进 我觉得程序员要有精益思维 增一分则多 减一分则少 如此种种改进 定能提高自己写代码 写出满意代码

    2018-12-25

  • belongcai蔡炳榕 👍(1) 💬(1)

    是的,我觉得码农的蜕变在于不断地迭代,提炼。

    2018-09-15

  • 慧敏 👍(0) 💬(1)

    太棒了,有很多令人思考的东西

    2022-11-05