真正优质的代码具备什么条件?如何高效的编写优质代码?——来自用户 yuklng
优质的代码基本的条件:轻量级、低耦合(松耦合)、易替换、易删除。
什么叫低耦合?
你如果可以从你的代码中删除某一模块而不用因此去重写其他模块的话,你的代码就通常被称为是低耦合的。
我们都喜欢轻量级的框架,因为你应该时刻保持着这样的警觉:迟早有一天,这个库、这坨代码会被替换、移除掉。
这可能让你联想起了 iOS 领域一个教科书级别的事件—— ASI 切换 AFN 。 ASI 曾经是 iOS 开发首选的第三方网络框架,后来作者宣布停止维护,开发者纷纷开始迁移到 AFN 。它教会我们一个道理:
让你的代码「易替换、易删除」,第三方框架代码质量是一方面,使用者也有责任。
想写优质代码,首先要敢于写垃圾代码。
正如程序员心中的上古巨神 Alen Jay Perlis 在 《 Epigrams on Programming 编程警句》 所说:
Everything should be built top-down, except the first time.
凡事都应该高屋建瓴,除非你是第一次干。
所以,在 beta 版本里犯错,上帝都会原谅。
不要在开始写一个应用之前就去想着能写一个万全的架构。道理很简单,就像你在早上 8:00 出门上班前许愿「一路绿灯」,即使当时愿望实现了, 8:00 全北京的灯都变绿了,但等你到下个路口,一样还是该红灯时红灯。我们是很难预先猜测,但我们却可以在发生小变化时,就及早去想办法应对发生更大变化的可能。也就是说,等到变化发生时立即采取行动。
第一次的时候尽管大胆的去写一堆乱七八糟的代码。
但垃圾代码始终还是要转换成优质代码的,如何转换?遵循「开放-封闭原则」,即 The Open-Closed Principle (简称 OCP )去转换:
这个原则其实是有两个特征,一个是说 「对于扩展是开放的, Open for extension 」,另一个是说 「对于更改是封闭的, Closed for modification 」。我们在做任何项目的时候,都不要指望项目一开始时需求确定就再也不会变化,这是不现实也不科学的想法,而既然需求是一定会变化的,那么如何在面对需求的变化时,设计的软件可以相对容易修改,不至于说新需求一来,就要把整个程序推倒重来。
上面是《大话设计模式》中的一段话,书中也给出了一个例子:
比如你在写一个加法程序,你很快在一个 client 类中就完成。此时变化还没有发生。然后我让你加一个减法功能,你发现,增加功能需要修改原来这个类,这就违背了「开放-封闭原则」,于是你就该考虑重构程序,增加一个抽象的运算类,通过一些面向对象的手段,如继承、多态等来隔离具体加法和减法与 client 耦合,需求依然可以满足,还能应对变化。这时我又要你再加乘除法功能,你就不需要再去更改 client 以及加法减法的类了,而是增加乘法和除法子类就可。即面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码这就是「开放-封闭原则」的精神所在。
你应该大胆尝试,在每一次尝试时都开新的坑,去犯新的错,然后通过迭代慢慢来完善。成为一个专业的软件开发者的过程就是不断积累后悔和「 error check list 」 的过程。你从「 Run Success 」身上学不到任何东西,知道优质代码长什么样,作用也微乎其微,你需要的是:对垃圾代码记忆犹新。
Perlis 也说过:
In programming, as in everything else, to be in error is to be reborn.
同时敢不敢写垃圾代码,完全取决于心态。写程序其实有两种心态,一种是做 Demo 的心态,一种是做项目的心态。让我们来对比下这两个心态:
对于业务逻辑,垃圾代码有时候是更好的选择,否则极小的封装都可以将你再次带入「过度封装」的陷阱。业务逻辑是那种有着无尽的 if else 、边界情况和快速而 dirty 的 hack 的代码。
现在你再回过头来思考下:一大坨垃圾代码,和过度封装的业务逻辑代码,哪个更好?
有时候删掉一个大的错误比删掉 18 个彼此相关联的小错误更为容易。
冗余一样被贴着「垃圾代码」的标签,难道冗余就没有一点好处?你必须试着拥抱合理的冗余。
我们会通过复制和粘贴来将部分代码重复使用以避免引入依赖性,提高灵活度,但代价就是冗余。
「一坨代码被使用两次,就应该封装」这个论调不应该被提倡。往往是刚开始很方便,越往后,随着需求的变更,越成为一种累赘。 为了一个小功能就去写一个库,甚至这个库只有一个方法,这样会带来什么问题?你削减了使用到了这个库的所有模块之间的独立性,这样做毫无必要。
代码越少, Bug 越少,代码也就越优质。
正如 iOS Architecture Patterns 这篇博客所言:
The less code you have, the less bugs you have.
The best code is the code that has never been written.
你应该始终明白什么是最优雅的解决方式。
我们构建模块不是为了复用,而是为了易于修改。
建工具类,出于两个完全相反的原因:低耦合、紧耦合。
低耦合
上面提到的「 ASI 切换 AFN 」教会我们:要为第三方库封装。尽量不要直接使用第三方库,而是为它们封装一层,通常我们叫这些类 Tool 、 Handler 、 Helper 、 Manager (先不吐槽命名了)。考虑到的是替换需求,由第三方库而衍生出来的工具类,通常也放在叫 Tool 的文件夹下。这样做的目的是尽可能将变化频繁的部分和相对更稳定的部分隔开来实现低耦合。这也同样适用于数据库以及各种可能会变更的 UI 组件。
紧耦合
但也不要一味追求「低耦合」,要根据实际情况来选择「紧耦合 tight coupling 」。比如错误处理就是一个需要与项目紧密结合在一起的操作。这个紧耦合的部分,我们就要放到 Tool 里,这也是建 Tool 的另一要考虑的原因:库总是试图迎合所有的需求,而我们只会用到其中小部分功能,而且只会对相应的响应做出特定的处理。所以建工具类可以隐藏不必要的细节。
好的 API 总是大而全,而建 Tool 则是我们意识到我们不能同时让所有人都高兴。开发一个好用的 API 和开发一个具有扩展性的 API 通常是互相冲突的。Tool 的作用就是让他们好用。
你在建 Tool 时应该已经体会到紧耦合的好处了,但有时 Tool 已经完全满足不了我们的紧耦合需求,这时我们就需要造轮子。
iOS 里 Star 数最多的一个库是 AFNetworking ,为什么?当年 iOS 5 、 iOS 6 时代还没有 NSURLSession 的时候, NSURLConnection 做断点续传和断点下载是非常困难的,而且苹果的 API 不对外隐藏任何细节,这也就造就了 AFNetworking ——它为大家隐藏了很多细节,所以好用。
同样的道理,在日益复杂化的 App 开发中, Auto layout 逐渐成为最佳选择。但复杂的 VFL 语法,以及开发者不太买账的 Storyboard 、 XIB 可视化操作,也成就了 Masorny 。 Masorny 以其优雅的链式纯代码操作,渐渐成为自动布局的主流框架。
设计模式的「单一责任」原则告诉我们:
每一个模块都应该只去解决一个难题
但我们更需要:
每一个难题都只应该由一个模块去解决
当一个问题需要两个模块去做的时候,通常都是因为改变一部分需要另外一部分的改变。
一个写得很糟糕但是有着简单接口的组件,通常比需要互相协调的两个组件更容易使用。
你应该像 AFNetworking 、 Masorny 那样,写出这样的优质代码:能将写起来、维护起来,或者删除起来最困难的部分互相隔离开。
大家可以通过问题提交通道来向 LeanCloud 提问。