2020年 我要这样写代码

在 9102 年年初,一位室友问我一个问题,如何才可以提高写代码的能力?html

惋惜的是: 当时仅仅回复了一些本身的想法,如多看开源代码,多读书,多学习,多关注业界的动向与实践,同时也列了一些原则。可是这些并无所总结,又或者说没有例子的语言始终是空泛的。因此在今年年末之际,对应着今年中遇到的形形色色的代码问题来一一讲解一下。前端

好代码的用处

实际上本书创建在一个至关不可靠的前提之上:好的代码是有意义的。我见过太多丑陋的代码给他们的主人赚着大把钞票,因此在我看来,软件要取得商业成功或者普遍使用,“好的代码质量”既没必要要也不充分。即便如此,我仍然相信,尽管代码质量不能保证美好的将来,他仍然有其意义:有了质量良好的代码之后,业务需求可以被充满信心的开发和交付,软件用户可以及时调整方向以便应对机遇和竞争,开发团队可以再挑战和挫折面前保持高昂的斗志。总而言之,比起质量低劣,错误重重的代码,好的代码更有可能帮助用户取得业务上的成功。

以上文字摘抄于《实现模式》的前言,距离本书翻译已经时隔 10 年了,可是这本书仍旧有着很大的价值。同时对于上述言论,我并不持否定意见。可是我认为,坏代码比好代码更加的费财(嗯,没打错,我肯定)。对于相同的业务需求,坏代码须要投入的精力,时间更多,产出反而会更少。同时根据破窗理论( 此理论认为环境中的不良现象若是被听任存在,会诱令人们仿效,甚至变本加厉 ),坏代码会产生更坏的代码。这是一个恶性循环,若是不加以控制,完成需求的时间会慢慢失去控制。须要完成需求的人也会失落离开。vue

也就是说,好代码能够实现多赢,可以让用户爽,可以让老板爽,可以让开发者爽。总之,你们爽才是真的爽。react

怎么写出好代码

少即便多

利用开源出来的设计与代码来减轻来自于业务线的时间压力。ios

The best way to write secure and reliable applications. Write nothing; deploy nowhere.

以上取自 github 上最火的项目之一 nocode。懒惰是程序员的美德之一。因此学习业务,理解业务,拒毫不必要的需求也是一个程序员的必修功课。详情能够参考如何杜绝一句话需求? 这一篇 blog,固然,在大部分场景下,咱们是不具有对需求说不的能力与权力的,可是不管如何,深度的理解业务,对客户有同理心是对程序员的更高要求。解决问题才是一个程序员须要作的事情。可以理解好题意才能解决问题。git

对于软件开发而言,时间必定是最宝贵,最有价值的资源。相应的,尽可能把时间耗费在解决新的问题,而不是对已经存在确切解决方案的问题老调重弹。因此,尽可能不要本身写代码,而是借用别人的设计与实现。而在事实上,你也很难在极短的时间压力下设计并完成比开源更加合适的代码。程序员

固然,开源做者必定是想让他的产品有更多的受众,因此从设计上而言,会采用较为通用的设计,若是你的需求较为特殊而且你以为不能说服做者帮你“免费打工”(或者做者拒绝了),那么你也只须要在特定之处进行包装与改写,可是要比彻底重写要简单太多了。github

固然,调研新的技术方案而且使用到项目中是一种能力,可是千万不要由于一个小功能添加一个很是大的项目。算法

笔者在以前就遇到过其余小伙伴由于没法使用数字四舍五入。说 fixed 方法有问题而使用 math.js 的小伙伴。typescript

(11.545).toFixed(2)  
// "11.54"

若是想要了解 fixed 方法为什么有问题的,能够参考 为何(2.55).toFixed(1)等于2.5? 做者以 v8 源码来解释为什么会有这样的问题,以及提供了部分修正 fixed 的方案。

事实上若是没有很大的精度需求,前端完彻底全利用一个函数即可以解决的问题,彻底不须要复杂的math 这种高精度库。

function round(number, precision) {  
 return Math.round(+number + 'e' + precision) / Math.pow(10, precision);  
}

固然,也有小伙伴来找我询问大量数据的表格优化,我第一反应就是 React Infinite 或者 vue-infinite-scroll 此类解决方案。可是对方可以多提供一些信息包括上下文,采用的技术栈,当前数据量大小,将来可能须要达到的大小,当前表格是否须要修改等。获得了这些信息,结合业务来看,相比于增长一个库,是否以下方式更为便捷与快速。

// 由于 vue 模型的缘由,使用 Object.freeze 性能能够有很大增益  
this.xxx = Object.freeze(xxx);

随着堆积业务,代码的增加。管理复杂度的成本与日俱增,把依赖下降。 利用开源代码使得任务更容易实现。时间就是成本。关键是让收益能够最大化。

学习更可能是为了作的更少。

统一

不一样的人因为编码经验和编码偏好不一样,项目中同一个功能的实现代码可能千差万别。可是若是不加以约束,让每个人都按照本身的偏好写本身的模块,恐怕就会变成灾难。

因此每次在学习一些新技术的时候,我老是想多看看做者的实例代码,做者是如何理解的,社区又是如何理解的。以求实现起来代码风格不至于偏离社区太多,这样的话能够提升沟通与协做的效率。相似于 《阿里巴巴Java开发手册》 或者 vue 风格指南 这种取自大公司或社区的经验之谈,要多读几遍。由于他们所遇到的问题和业务更加复杂。

对于公司内部开发来讲,写一个组件时候,生命周期的代码放在文件上面仍是放在最下面,如何把代码的一个功能点集中放置。通用型代码的修改。代码行数的限制。可以列出统一的方案,多利而少害。

化繁为简(抽象)

抽象是指从具体事物抽出、归纳出它们共同的方面、本质属性与关系等,而将个别的、非本质的方面、属性与关系舍弃的思惟过程。

若是你面对一个较大的系统,你会发现重构并不能解决根本问题,它仅仅只能减小少量的代码的复杂度以及代码行数,只有抽象才能够解决实质性问题。

不管是数据库设计,架构设计,业务设计,代码设计,但凡设计都离不开抽象。抽象能力强的所面临的困难会比能力弱的少不少。

或者说抽象能力弱一些的小伙伴遇到一些问题甚至须要从新推翻而后再设计,这个是在时间和业务开发中是不能被接受的。

这里就谈谈代码,如下也举个例子,如 axios 库中有拦截器与自己业务,在没有看到源码以前,我一直认为他是分 3 阶段处理:

  • 请求拦截
  • 业务处理
  • 响应拦截

但若是你去看源码,你就会发现其实在做者看来,这 3 个阶段其实都是在处理一个 Promise 队列而已。

// 业务处理  
var chain = [dispatchRequest, undefined];  
var promise = Promise.resolve(config);  
​  
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {  
 // 前置请求拦截  
 chain.unshift(interceptor.fulfilled, interceptor.rejected);  
 });  
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {  
 // 后置响应拦截  
 chain.push(interceptor.fulfilled, interceptor.rejected);  
 });  
​  
 while (chain.length) {  
 promise = promise.then(chain.shift(), chain.shift());  
 }  
​  
 return promise;

这就是一种代码抽象能力。让本身的代码能够适应更多的场景是程序员须要思考的。代码不是给机器看的,是给人看的,更高的要求是: 代码不只仅是给人看的,更是给人用的。须要考虑到协做的人与事,灵活的配置也是必需要考虑到的。就拿前端的 虚拟 dom 来讲。可以适配更多的平台。

固然了,抽象能力须要时间,须要经验,须要学习大量的设计。

注意!:不要过早的抽象业务代码,甚至不要抽象业务代码。多写一点代码无所谓,千万别给本身找事作。 在业务上尽可能保持简单和愚蠢。除非你是业务专家,确认当前业务不太会产生变化。

权责对等(拆分与合并)

责任与义务本质上就是对等的,且越对等的就越稳定。这几年,微服务架构,中台,微前端理论层出不穷,本质上就是为了权责对等,对于更加基础的服务,更有产出的业务投入更高的人力与物力以保证更稳定的运行是很正常的一件事。而不是以前的大锅饭(单体应用)。

从代码上来看,某个模块是否承担了它不该该作的事情,或者某个模块过于简单,徒增复杂度。

固然,事实上有些东西目前是作不到的让全部人都以为满意,增一分则肥,减一分则瘦,刚恰好很难界定。就像 Dan Abramov 说的那样:

Flux libraries are like glasses: you’ll know when you need them.

只作一件事

Unix 哲学,这个很好理解,就像我今年想作的事情太多,反而什么都没有作(或者说都作了,但都很差)。

代码上来看,不要由于一点点性能的缘由,把几件事合在一块儿去作。例如在一次 for 循环中解决全部问题,或者将全部代码写在一个函数中,例如:

created() {  
 const {a,b,c,d} = this.data  
 // ... 三件事情彼此有交互同时须要 a,b,c,d  
   
 // 完成以后的逻辑  
}

改造后:

created() {  
 const axx = doA()  
 doB()  
 const cxx =  doC()  
 // 完成以后的逻辑  
}  
​  
// 分离出3个函数  
doA() {  
 const {a,b,c} = this.data  
 // ... 三件事情彼此有交互同时须要 a,b,c,d  
   
 // 完成以后的逻辑  
}  
// 其余代码

相比于第一个只须要一次取数,一次setData,第二个性能无疑更低,可是可维护性变高了,3 件事情都被拆分出来,后面修改代码时候,我能够追加一个 doD 而不是再次把第一份代码中逻辑整理清楚再当心翼翼的修改代码。

命名与注释

There are only two hard things in Computer Science: cache invalidation and naming things.

命名与缓存失效是两大难题,今年讲了很多缓存问题,同时,命名的确是很困难的一件事情。经过一句话来解释大家在作什么事情,经过一句话来解释一件事的意图。

不说在程序世界中,在现实世界中也是如此。例如: 《震惊!xxx竟然xxx》等新闻,虽说看完后都会想要骂一句,可是,正如这样的名字才能吸引人家点击进入,让人不由自主的被骗一次又一次。因此在项目没有发布前,要取一个简单而又好记的名字。

但在程序内部,咱们不须要“骗取”人家的点击量,反而是要务实点,不要欺骗另外的同伴,好比说写了一个简单的名字,结果内部却封装了不少的业务代码。同时我认为这也是函数越写越短的理由,由于你们难以经过命名来解释那一大坨代码的意图。因此,须要编写能够自我解释的代码,而这种代码最佳实践就是好的命名。

对于开源代码,你每每会发现,这些文件开头都会有一系列注释,这个注释告诉咱们了这个模块的意图与目的。让你无需看代码就能够进行开发。

对于业务开发而言,仅在你不能经过代码清晰解释其含义的地方,才写注释。在多个条件下都没法解释你的代码。

  • 项目名
  • 模块名
  • 文件名(类名)
  • 函数名(方法名)

这并非让你不写注释。可是我以为更多的注释应该放在数据结构而不是代码逻辑上。聪明的数据结构和笨拙的代码要比相反的搭配工做的更好。更多的时候,看数据结构我能了解业务是如何运行的,可是仅仅看到代码并不能实际想象出来。

实际上,随着时间的推移,代码作出了许多改动,但注释并无随之修改,这是一个很大的问题。注释反而变得更有欺骗性。

这里也提供一篇 export default 有害 的文章。我以为 export default 导出一个能够随意命名的模块就是一种欺骗性代码(随着时间的推移,该模块的意图会发生变化)。

考虑场景

没有放眼四海皆准的方案,因此咱们必需要考虑到场景的问题,咱们老是说可修改性,可读性是第一位的(每每可读,可修改的代码性能都不差)。可是若是是急切需求性能的场景下,有些事情是须要再考虑的。

if 是业务处理中最经常使用的,在每次使用前要考虑如下,哪一个更适合做为主体,哪一个更适合放在前面进行判断。若是有两个维度上的参数,一个是角色,一个是事件。必定是会先判断角色参数,而后再去判断事件参数,反之则必定很差。由于前者更符合人的思惟模式。在同一维度下,至于哪一个放前面,必定是更多被使用的参数放在前面更好,由于更符合机器的执行过程。

就像在 if 中你到底是使用 else 仍是 return。大部分状况下处理业务逻辑互斥使用 else,处理错误使用return。由于这样的代码最符合人的思惟逻辑。

可是在这里我也要举出来自《代码之美》的例子,在第五章中,做者 Elliotte Rusty Harold 设计了一个 xml 验证器,其中有一段在验证数字字符:

public static boolean isXMLDigit(char c) {  
 if (c >= 0x0030 && c <= 0x0039) return true;  
 if (c >= 0x0660 && c <= 0x0669) return true;  
 if (c >= 0x06F0 && c <= 0x06F9) return true;  
 // ...  
 return false  
}

这个优化以后以下:

public static boolean isXMLDigit(char c) {  
 if (c < 0x0030) return false; if (c <= 0x0039) return true;  
 if (c < 0x0660) return false; if (c <= 0x0669) return true;  
 if (c < 0x06F0) return false; if (c <= 0x06F0) return true;  
 // ...  
 return false  
}

全局思考,善于交流

软件开发已经不是一我的打天下的时代了,你要不停的触达边界。在先后端分离的时代,前端能够不知道数据库如何优化,后端也能够不清楚浏览器的渲染机制,可是却不能不明白对方在作什么。不然等于鸡同鸭讲,也会浪费时间。在开发时候,把一段逻辑放在那一端取决安全的思考以及简化逻辑。

善于交流是一种能力,在与别人交流时给与足够的上下文,让你的 leader 沟通,让她知道你的难处。和小伙伴沟通,说服他人按照你的想法推动,同时,善于聆听才能不断进步。

算法

我不是一个算法达人( leetcode 中等题目都费劲 ),但这个没什么可说的,你拿你的 O(n**3) 算法去对战人家 O(n * logn) 算法就是费财。因此,知道本身某方面不够好去努力就好了。

辅助工具

TypeScript

虽然早就接触和实践过,可是以往都是 AnyScript。今年也算重度使用了。才体会到该工具的利好。一个好的开发工具并非让你少写那一点点代码,而是让你在交付代码时候可以更加自信。

TypeScript 最大的好处就是让你在写代码前先思考,先作设计。就像以前说的。聪明的数据结构和笨拙的代码要比相反的搭配工做的更好。

TypeScript 同时也可让大部分运行时错误变为编译时,而且能够减小使用中的防护性编程(信任可是仍要验证)。你不是一我的在写代码,协做优先。

在开发中,若是你接触过复杂性数据结构,而且还要在模块中不断进行数据转化,你就会不断的遇到:个人数据呢?到底在那一步丢失了?而且即便是代码对的,你仍旧惧怕,仍旧怀疑。我已通过了那个“写 bug 是由于想的不够多,不够完全”的年龄。

函数式思惟

js 是有函数式的血统的,当年一直据说,函数是一等公民,只是当时彻底不能理解。

纯函数,数据不可变以及代码即数据这三点是我认为是函数式思惟对代码能力提高最大的三点。

这个我不想展开去聊,由于我没有熟练掌握过任何一门纯函数式语言。可是个人代码必定有函数式的影子,而且它的确让个人代码更优美。

其余

单元测试,代码审查,安全等等都没有讲到,这个我也须要足够的学习才能有所输出。不过这里列出一些资料供你们学习与了解:

谷歌代码审查指南

SaaS型初创企业安全101

有理有据就是好代码

工做在别人遗留的糟糕代码上是常有的事情,同时面对开发需求实际表,为了兼容,咱们也不得不写出一些不那么好的代码。可是面对他人的疑问,咱们须要给与别人这样作的理由,也就是你的每一行代码写下去必定有充分的理由和依据。

结语

明显不等于简单,上述都是很明显的事情,可是要作好都须要很长时间的学习与经验。

因此如何才能写好代码呢?那就是多看开源代码,多读书,多学习,多关注业界的动向与实践。不断学习,不断进化的代码才是好代码。

最近有一些小伙伴(无中生友)问个人名字为何要叫 jump_jump。我是为了让本身以及看到个人小伙伴们牢记多锻炼,闲下来的时候多跳一跳。对身体有好处。

鼓励一下

若是你以为这篇文章不错,但愿能够给与我一些鼓励,在个人 github 博客下帮忙 star 一下。 博客地址

相关文章
相关标签/搜索