鹅厂内部,有一个关于“陷入了写代码的完美主义陷阱怎么办”的帖子,题主是这样写的:
自认为代码编写和设计能力不弱,一般的代码逻辑也比较清晰。
但是当要设计一个略大的项目,或者接手一个相对较新的代码,想要适当的做一些较大的重构的时候,就总是会感觉这样也不好,那样也不好,怎么做都会有一些缺陷,难以下笔。
虽然能合理的拆分成几个模块,但是对每个模块的代码怎么写还是十分纠结,然后总觉得自己还是思路不够清晰,没有想清楚怎么继续调整,空耗了一些时间,最后还是以一个自己不满意的结构写完,这个过程中虽然有思考,但是有明显的局限,也导致效率有下降。
求教:如何提升代码能力,以及如何克服这种又菜又想追求完美导致低效的问题。
这大概是很多技术同学都想了解的问题,那么如何解决这个问题呢?我们总结了鹅厂程序员们的方法:
1. 一边写代码,一边继续思考
@Raymond.
看到问题比较能感同身受,因为我记得自己有一个阶段也是这样。可能是对“代码完美主义”心有余悸,我在写完自己的书《Python 编程进阶相关》时,还特意在结尾时留了一页,这么写道:
> 本书的最后,我想额外啰唆几句。
> 虽然本书自始至终都在说“如何把 Python 代码写得更好”这件事,但我还是希望最后提醒你一句:不要掉进完美主义的陷阱。因为写代码不是什么纯粹的艺术创作,完美的代码是不存在的。有时,代码只要能满足当前需求,又为未来扩展留了空间就足够了。
“完美主义”如何产生危害?主要在于当我们面对规模较大的任务时(比如你提到的“设计大项目”、“较大的重构”),盘旋在脑海里的想法和声音太多了,每一个都在对我们说:“这样做最好,能保你之后万事无忧!”。在它们的包围中,我们先是花了大量时间纠结,之后无论做出哪种选择,似乎都“不够好”——不仅效率受到影响,心理上也十分难受。
要从“完美主义”的消极影响里走出来,最简单的办法就是不管它:继续写代码,继续纠结。当你重复做一件事情足够多次以后,那些宝贵的经验会自然而然地让你跳出“完美主义”,不再纠结。
除了放任“完美主义”不管之外,另一个行之有效的办法则是“测试驱动开发(TDD)”。采用 TDD 后,你能更流畅地在两种角色之间转换:设计者(编写测试时)和实现者(编写代码时)。不同的角色能有效为你的思维“设限”,让你更清晰地思考,从而打磨出更好的设计。
除此之外,TDD 对于克服“完美主义”还有以下优势:
- 编写测试本身就有助于写出耦合更低、结构更优良、更趋近于“完美”的代码
- 当你纠结于代码好坏时,单元测试会像一位旁观者一样告诉你:“代码没毛病,别瞎纠结。”
- 当你发现旧设计有问题,想重构时,单元测试也会辅助你在追求“完美”的路上万无一失
最后,“完美主义”虽然有一些坏处,但适度追求完美也是必要的。反过来说,如果大家写代码时从不纠结,关于代码的最高指导思想就四个字:“能跑就行”,这样也很可怕吧。
@Gaidong.
绝大多数好的设计不是一蹴而就的,而是逐步演进出来的,除非要应对的场景本身就在我们的经验范围之内。
大致路线可能是:先理解清楚业务需求,调研业界类似场景的解决方案。如果有解决方案,那这个设计一般已经是在别人那里演进过多轮了,结合自身场景的特殊性,加以适配使用现场的方案即可。如果没有解决方案,那首先应该感到兴奋,因为你正在做的事情很可能是在创新或者至少是微创新。这时候先设计一个版本把系统跑起来,后续迭代中再逐步去优化也是无可厚非的。
上述过程中,可能方案调研是一个关键环节了,就像写论文中的综述部分。
2. 给代码一个进化的过程
@Aproom.
当接到完整的需求或者接手已有项目的代码,这种情况会存在两个问题:
一是大量需求并不是你和需求提出者一点点讨论、磨合出来的,没有经过长时间的需求分析、讨论,对需求的理解不够深刻。
二是在全部需求都已知的情况下你试图一下子设计一套完整的结构、框架,不像新项目先设计一个简单但灵活的框架,然后随着需求的滚动增长不断调整设计、不断重构,同时也对需求理解更加深刻,我管这个叫做“给代码一个生长、发育的过程”或者“给代码一个进化的过程”。
想一步到位设计出完美的架构是不可能的,编程最大的技巧就是无限深入需求,不断思考需求,让代码从小到大不断发展、重构,当然很多时候客观条件不允许,那就放下对完美代码的执着吧。
顺便说一句,我反对大部分代码重构的原因是:大部分重构人只是新接触一个项目,重构的理由有时是“我比较闲、有时间”,或者对之前的设计感到不舒服。但他们缺乏重构的最重要条件,就是对需求比以前更加深刻的理解,和需求摸爬滚打在一起的决心。
@Zelin.
引用 React 官网的一段话,也作为我陷入类似情况的一个破局之道——不要过度思考。
如果你刚刚开始一个项目,不要花超过五分钟在选择项目文件组织结构上。选择任何一种模版结构(或提出自己的方式)并开始编写代码。因为,在你编写了一些真正的代码之后,你将很有可能会重新考虑它。
如果您感觉完全卡住,请先将所有文件保存在同一个文件夹中。它最终会变得足够大,以至于让你想要将其中一些文件拆分出去。到那时,你将有足够的知识去区分你最频繁编辑的文件。通常,将经常一起变化的文件组织在一起是个好主意。这个原则被称为 “colocation”。
随着项目规模的扩大,人们通常会在实践中混搭使用各种方式。因此,在开始时选择“正确”的那个方式并不是很重要。
3. “够好即可”不意味着糟糕的代码
@Luxx.
如《IEEE 软件》杂志上一篇由爱德华·尤登写的文章《够好即可的软件就是最好的》所述:
你能训练自己写出够好即可的软件——对用户、未来的维护者来说够好即可,只要好的程度能让你自己内心平静就可以。你会发现,你变得更有效率,用户也更快乐。而且,可能让你更开心的是,更短的孵化期促使你的程序实际上更好了。
在进一步讨论之前,我们需要对将要讨论的内容做一些限定。“够好即可”这个词并不意味着草率或糟糕的代码。所有系统必须达到用户的需求才算完成,需要达到基本的性能、隐私和安全标准。你做的东西,从用户需求角度来说是否足够好?最好还是留给用户一个机会,让他们能亲自参与评判。
与构想中的明天那个完美的软件相比,今天就还不错的软件通常更讨人喜欢。如果你早点给用户一点东西玩,他们的反馈常常能引领你做出更好的最终方案。
—— 《程序员修炼之道》 第 51 页 话题 12:曳光弹
@Xiaoning.
《重构》的作者Kent Beck的有个“两顶帽子”说法我觉得很有道理:就是软件开发的过程应该有两顶帽子换着戴,一顶是开发新功能的帽子,一顶是重构的帽子。
简单来说,就是楼主想添加新功能时,先不管实现是否优美,先把功能给写了;然后再切换成重构帽子,把它优化一下。注意,这两顶帽子是不断快速切换的,不是说你一下子写完好大一个模块再重构它,那是不行的。很多作家写作时大体也和这差不多。
我觉得挺好用的。但是实践中也有很多别的问题,比如开始选的不好后面不好改啊,写出来难得重构啦,选来选去也选不好啊等等。我觉得这些可以靠经验解决啦,如果很熟练的话就应该就能做到胸有成竹了。作为一个小菜鸡,我还不够有经验,我希望我有一天能做到胸有成竹。
@Cheater.
分享一下我的见解:
(1) 因为“设计一个略大的项目,或者接手一个相对较新的代码”是一个开放性的问题,较为抽象的问题。“代码编写和设计能力不弱”对于“维护老的项目,做一些需求的改动”这种依赖边界清晰的、具体的问题是足够的。要解决抽象的问题,还需要‘分析问题的能力’,以及‘创新能力’--解决新问题。
(2) 要能很好地认识新问题,你需要给自己时间思考、分析问题。不要急于写下第一行代码。
(3) 我们不要从头发明看似简单的‘牛顿经典力学’,从课本里学习,初中生就能掌握,自己去思考,可能一辈子也不够。所以,面对新问题,我们首先要去调查其他人遇到过么,积累了哪些思考、实践、认知。
(4) 工程师的能力的提升,就是解决越来越抽象的、边界不清晰的、依赖不清楚的、依赖众多的问题。比如,极度抽象的,spaceX项目。