如何提高代码品味

如何提高代码品味

一家之言,可以在评论里探讨

写代码虽然大多数时候是个体力活,但不可否认,也需要一点品位。我曾经觉得代码质量很重要,后来写业务写多了,又觉得如果连代码正确都做不到,又谈何代码质量。后来我又醒悟了,这世上很难有 bug free 的代码,当出现 bug
的时候,好代码比烂代码会好改很多。我们今天就讨论下什么是好代码,毕竟一个不知道什么样的代码是好代码的人是不可能如有神助写出好代码的,写代码可以搜索复制黏贴三板斧,写好代码却是必须刻意练习的。

什么是写代码

我觉得写代码分为两个部分:

  • 结构设计,包括模块划分、模块交互、接口设计
  • 功能实现,涉及具体的语言特性以及代码风格

结构设计

所谓的结构设计不是说一定要画个架构图,写个系分文档什么的,结构设计和功能实现其实螺旋贯穿在整个写代码的过程中。当我们准备完成一个需求的时候,会把需求分成几个功能,这些功能如果互相独立,便不涉及交互,否则他们之间就需要沟通,可能是直接调用,可能是发送消息,可能是监听变化,可能是轮询结果等等。分了功能之后,要实现其中某个功能,又要递归的执行一遍上述过程,直到写下一行行代码。有同学可能觉得这种自顶向下的过程太宏观了,前期太费时间,什么模块什么交互,我就挑个功能一把梭,代码先写起来。这当然也可以,而且大多数人都是这么做的。但这其实也包含结构设计,你准备率先实现的那个功能,潜意识里你已经把它从整个系统中分离出来了,只是系统的其他部分暂时先不管而已。模块划分是个说烂的话题,但它又真的是软件工程的精髓,它的意义在于,人管理复杂度的能力是有限的,当一大坨代码怼在一起的时候,哪怕代码质量再高,注释再详尽,也会引起生理上的不适。这种不适最容易发生在当你要修改一个小功能,找了半天代码找不到的时候。划分了之后,哪怕是好几坨烂代码,但你改动的时候只改其中一个文件,其他代码也是眼不见心不烦。

那模块如何划分?我们可以说出一些普适的原则,譬如高内聚低耦合、单一职责原则、开闭原则等等,但这些东西说起来感觉很套路很不真诚,让人觉得无从下手。我个人觉得有两条很重要的原则:

  • 开发的过程中时刻反思自己的代码结构
  • 认真命名

关于反思代码结构,最重要的当然是自己的思考,不要迷信别人给你做的设计,也不要迷信自己当初的设计,我大概列举几种情况:

  • 一开始分了两个模块,写着写着发现其中一个模块的体积很大,那就看看能不能再继续分
  • 一开始分了四五个模块,后来写着写着发现其中两个模块交互巨频繁,他们必须一起配合才能实现一个完整的功能,那就把他们合在一起(高内聚)
  • 在迭代的过程中发现两个模块虽然相互独立,但交互逻辑写得很死,依赖关系很直接,修改了一个,另一个也必须改,那就修改他们的依赖和交互,尽量做到互不影响(松耦合)
  • 在迭代过程中发现两个模块的某部分是可以共用的,那就抽出来(DRY)
  • 在迭代过程中发现某个模块的一段代码变更很频繁,那就单独把这部分抽出来(封装变化)
  • 。。。

这些情况当然列举不完,团队中其实可以定一些硬性指标来辅助模块划分,譬如一个文件最多 300 行,一个函数最多 70 行什么的,放在 Lint 规则里。我们有很多约定俗成的“潜规则”其实都有它背后的逻辑,譬如以前天天说的 MVC 和 MVVM 两个模式,他们的最主要区别不在于模块划分,而是模块间的交互,在划分方面它们都致力于让 UI 和逻辑分开,为什么呢?因为 UI is cheap,UI 是隔三差五会被设计师推翻再来一套的,但逻辑和数据,相对会稳定一点,所以把他们分开,UI 迭代的时候涉及的改动就比较小。那为什么前端的 React/Vue
这些近年大火的框架,又提倡所谓的组件(Component),貌似 是要把 UI 和逻辑搞在一起呢?其实不是的,组件是一个小粒度的模块,它相对独立,具有很高的内聚性,组件中的“逻辑”更多的应该是 UI 相关的交互逻辑,而不是那些比较底层的逻辑,我们还是应该把相对稳定的逻辑抽出来放到更下层。

关于命名,很多同学可能不在意,觉得代码能跑就行了,取名字有什么关系,看不懂我加注释嘛。这是非常不好的习惯,因为命名的过程中其实就是在概括你这段代码,如果你的某个函数名叫 xxxAndxxx
那这个函数就应该被拆成两个函数,它明显违反了单一职责原则。大到业务模块小到辅助函数,只要你觉得不好命名,那就是一个信号,说明这段代码做的事情太多太杂,以至于你无法用几个单词概况出来。

功能实现

现在我们具体聊聊代码实现的时候怎么体现代码品位,我觉得主要可以从三点着手提高:

  • 精通你所用的编程语言
  • 提高自己的逻辑能力
  • 注重代码风格

有一个广为人知的观点,编程思路最重要,语言只是工具。乍一听,编程语言似乎无足轻重。如果只是一锤子买卖,写段代码实现个功能,写完离手,再不相干,那语言当然不重要。我相信大多数码农都可以很快上手一个新语言,因为要实现一个功能可能只需要一些通用的核心特性,对 C 系语言来说,知道函数/分支语句/循环语句/字符串/数组/散列表这些东西的使用就足够开发日常需求了,而这些特性说实话在 C
系语言中都大同小异。但如果要写好代码,就得向着精通这门语言努力。有些功能你写一堆蹩脚代码可能实现得马马虎虎,但用了某个特性,几行短小精悍的代码就解决了。我很排斥一个观点是,为了让团队的所有人都能看懂,鼓励只使用语言的基本特性,一些高级的或者不太常用的特性不准用,用了就是炫技。举个极端点的例子,有人觉得 if else 比三元表达式更可读,就鼓励只使用 if else。这样的“可读”在我看来只是迎合平庸的码农。有些语言特性确实有利有弊,有的甚至只有弊(JS
中就很常见),那尽量不用,或者根据实际场景做取舍。我们取舍的标准是“场景”,而不应该是人。什么叫合适的场景呢,还是拿三元表达式举例:

const data1 = a > b ? 100 : 200;
const data2 = a > b ? 300 : 400;

我看到过有同学这样用,这段代码虽然只有两行,但 a > b 这个条件要判断两次,性能我们且不论(a > b 只是个示例,实际的条件可能更复杂),至少已经重复了,写的时候写两次,读的人也要看两次,不如就

let data1 = 200;
let data2 = 400;
if (a > b) {
    data1 = 100;
    data2 = 300;
}

或者把 a > b 这个 condition 提前计算好,避免计算两次(这种优化不是针对性能,因为有些语言编译器在编译期会做类似这种优化,主要还是可读性):

const condition = a > b;
const data1 = condition ? 100 : 200;
const data2 = condition ? 300 : 400;

代码要简洁,但不是简短(代码行数少),简洁的意思是逻辑清晰,没有冗余信息。还有一个场景是我有看过同学用嵌套的三元表达式来替代多个 if else 的操作,那真的是可读性很差了,不要这样。

这方面也有一些具体的技巧,譬如我个人很喜欢用散列表去代替分支语句:

// if (key === 'x') return 'xxx';
// if (key === 'y') return 'yyy';
// if (key === 'z') return func;

const xxxMap = {
    x: 'xxx',
    y: 'yyy',
    z: func
};
return xxxMap[key];

这种做法好像有个名字叫“表驱动设计”,这样做有很多好处,代码简洁是一点,这个 xxxMap 其实是一张配置表,以后可以抽出去放到单独的地方,甚至放到服务端去配置,就可以很容易实现一些动态化需求。更延伸开去讲,能配置化的东西尽量都配置化,方便以后扩展功能。

再举个 reduce 的例子,如何根据场景选择:

const reservedWords = ['initialState', 'state', 'effects', 'actions', 'updates', 'mutations'];

// 这里其实用 reduce 会好看些:
// store => reservedWords.reduce((acc, x) => acc || store.hasOwnProperty(x), false)
// 但为了能提前返回,性能稍微好一点,还是用了 for
const isSingleStore = (store) => {
    for (let i in reservedWords) {
        if (store.hasOwnProperty(reservedWords[i])) return true;
    }
    return false;
};

说了语言重要,那思路呢?当然也重要。我们日常生活中,逻辑清晰的人三言两语就直击要害,逻辑混乱的人兜兜转转还是云里雾里。代码也一样,有些糟糕的代码是糟糕在啰嗦,一个判断能搞定的事情它可能要变着法判断两三次。这种呢,就是本身思路不简洁,写出来的代码自然也简洁不了,只能努力提高自己的逻辑能力。

剩下的代码风格,也非常重要。长得好看的人是有特权的,长得好看的代码自然也有。多看看官方或者大厂的 Style Guide,平常多注意空格换行缩进命名风格等等,装个优秀的格式化插件也好。

差不多就先这样吧,品味不见得有好坏,但有高低,共勉。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,471评论 2 59
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 【朋朋日历】 2018 年 9 月 17 日 星期一 农历 八月初八 戊戌年 辛酉月 壬子日 **********...
    刘书朋阅读 143评论 0 0
  • 尤记得很小的时候,周围的大人老是逗我,说我是桥底下捡的,爸爸妈妈不喜欢你才不把你带身边。我那时候说:你们都不爱我,...
    缩沙蜜阅读 279评论 0 1