本文作者:sukai

javascript编程指南(javascript编程技术)

sukai 2023-09-25 101

今日

我们要聊一个简单但又不失深奥与思考的问题

进阶成为一名优秀的码农

应该具备哪些基本的素养?

  

没错

正如上图

作为一名优秀的码农

应具备

码得出代码,

看得懂自己码的代码,

让别人看得懂你码的代码,

这三个基本素养。

  提高自身的编码能力和编写易于阅读和维护的代码,是广大码农们提高开发效率和职业身涯中必做的事情。

  那么究竟如何编写出可维护的、优雅的代码呢?

  避免使用 JS 糟粕和鸡肋

  这些年来,随着HTML5和 Node.js 的发展,Java 在各个领域遍地开花,已经从“世界上最被误解的语言”变成了“世界上最流行的语言”。

  但是由于历史原因,Java 语言设计中还是有一些糟粕和鸡肋。比如:全局变量、自动插入分号、typeof、NaN、假值、==、eval等等,并不能被语言移除,开发者一定要避免使用这些特性。

  编写简洁的 Java 代码

  以下这些准则来自 Robert C. Martin 的书 “Clean Code”,适用于 Java。整个列表很长,我选取了我认为最重要的一部分,也是我在项目用的最多的一部分,但是还是推荐大家看一下原文:

  https://github.com/ryanmcdermott/clean-code-java

  这不是风格指南,而是 使用 Java 生产可读、可重用和可重构的软件的指南。

  变量

  使用有意义,可读性好的变量名

  bad 示例:

  good 示例:

  使用 ES6 的 const 定义常量

  bad 示例:

  good 示例:

  使用易于检索的名称

  bad 示例:

  good 示例:

  

  使用说明性的变量 (即有意义的变量名)

  bad 示例:

  

  good 示例:

  

  方法

  保持函数功能的单一性

  这是软件工程中最重要的一条规则。

  当函数需要做更多的事情时,它们将会更难进行编写、测试、理解和组合。

  当你能将一个函数抽离出只完成一个动作,他们将能够很容易的进行重构并且你的代码将会更容易阅读。

  如果你严格遵守本条规则,你将会领先于许多开发者。

  bad 示例:

  

  good 示例:

  

  函数名应明确表明其功能(见名知意)

  bad 示例:

  

  good 示例:

  

  使用默认变量替代短路运算或条件

  bad 示例:

javascript编程指南(javascript编程技术)

  

  good 示例:

  

  函数参数 (理想情况下应不超过 2 个)

  限制函数参数数量很有必要,这么做使得在测试函数时更加轻松。

  过多的参数将导致难以采用有效的测试用例对函数的各个参数进行测试。

  应避免三个以上参数的函数。

  通常情况下,参数超过三个意味着函数功能过于复杂,这时需要重新优化你的函数。当确实需要多个参数时,大多情况下可以考虑这些参数封装成一个对象。

  bad 示例:

  

  good 示例:

  

  移除重复代码

  重复代码在Bad Smell 中排在第一位,所以,竭尽你的全力去避免重复代码。因为它意味着当你需要修改一些逻辑时会有多个地方需要修改。

  重复代码通常是因为两个或多个稍微不同的东西, 它们共享大部分,但是它们的不同之处迫使你使用两个或更多独立的函数来处理大部分相同的东西。

  移除重复代码意味着创建一个可以处理这些不同之处的抽象的函数/ 模块/ 类。

  bad 示例:

  

  good 示例:

  

  避免副作用

  当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。

  比如写文件、修改全局变量或将你的钱全转给了一个陌生人等。程序在某些情况下确实需要副作用这一行为,这时应该将这些功能集中在一起,不要用多个函数 / 类修改某个文件。用且只用一个 service 完成这一需求。

  bad 示例:

  

  good 示例:

  

  避免条件判断

  这看起来似乎不太可能。

  大多人听到这的第一反应是:“怎么可能不用 if 完成其他功能呢?”

  许多情况下通过使用多态 (polymorphism) 可以达到同样的目的。第二个问题在于采用这种方式的原因是什么。答案是我们之前提到过的:保持函数功能的单一性。

  bad 示例:

  

  good 示例:

  

  使用 ES6/ES7 新特性

  箭头函数

  bad 示例:

  

  good 示例:

  

  模板字符串

  bad 示例:

  good 示例:

  解构

  bad 示例:

  

  good 示例:

  使用ES6 的 classes 而不是 ES5 的 Function

  典型的 ES5的类 (function) 在继承、构造和方法定义方面可读性较差,当需要继承时,优先选用classes。

  bad 示例:

  good 示例:

  

  Async/Await 是比 Promise 和回调更好的选择

  bad 示例:

  

  good 示例:

  

  Babel

  ES6 标准发布后,前端人员也开发渐渐了解到了 ES6,但是由于兼容性的问题,仍然没有得到广泛的推广,不过业界也用了一些折中性的方案来解决兼容性和开发体系问题。其中最有名的莫过于 Babel了,Babel 是一个广泛使用的转码器,他的目标是使用 Babel 可以转换所有 ES6 新语法,从而在现有环境执行。

  Use next generation Java, today

  Babel 不仅能够转换 ES6 代码,同时还是ES7的试验场。比如已经支持async/await,使开发者更容易编写异步代码,代码逻辑和可读性简直不能太好了。

  虽然主流浏览器可能还需要一段时间才能支持这个异步编码方式,但是基于 Babel,开发者现在就可以在生产环境使用上它。这得益于Babel 与 Java 技术委员会保持高度一致,能够在 ECMA 新特性在标准化之前提供现实世界可用的实现。

  因此开发者能在生产环境大量使用未发布或未广泛支持的语言特性,ECMA 也能够在规范最终定稿前获得现实世界的反馈,这种正向反馈又进一步推动了 Java 语言向前发展。

  Babel 最简单的使用方式如下:

  

  在当前目录下建立文件.babelrc,写入:

  

  ESLint

  一个高质量的项目必须包含完善的 lint,如果一个项目中还是 tab、两个空格、四个空格各种混搭风,一个函数动不动上百行,各种 if、嵌套、回调好几层。加上前面提到的各种 Java 糟粕和鸡肋,一股浓厚的城乡结合部风扑面而来,这还怎么写代码,每天调调代码格式好了。

  因此lint 非常有必要,特别是对于大型项目,他可以保证代码符合一定的风格,有起码的可读性,团队里的其他人可以尽快掌握他人的代码。

  对于 Java 项目而言,目前 ESLint 将是一个很好的选择。

  

  

  Prettier

  Prettier 一个 Java 格式化工具. 它的灵感来源于 refmt,它对于 ES6、ES7、 JSX 和Flow 的语言特性有着高级的支持。

  通过将 Java 解析为AST并且基于 AST美化和打印,Prettier 会丢掉几乎全部的原始的代码风格,从而保证 Java 代码风格的一致性,你可以先感受一下。

  

  以上命令会在 pre commit 时先执行 Prettier 格式化,然后再执行 ESLint 的校验。如果想要在编辑时就格式化代码,Prettier 针对当前主流编辑器也有插件,请参考它的 Readme文档。

  另外 ESLint 可以和 Prettier 很好的搭配使用,参考 eslint-plugin-prettier ,以上所有的配置和文件我都整理到了这个项目里:

  https://github.com/ingf/elegant-writing-java

  采用函数式编程

  在谈到函数式编程及其有什么优点之前,我们先看我们常见的编程方式,imperative programming(命令式编程)有什么缺点。

  

  这段代码很简单,它过滤一个传入的数组,取出里面每个元素的data 域,然后插入新的数组返回。相信很多人都会撰写类似的代码。它有很多问题:

  1.我们在告诉计算机怎么样一步步完成一件事情,引入了循环,使用一个无关紧要的局部变量 i 控制循环(或者迭代器)。事实上我根本不需要关心这个变量怎么开始,怎么结束,怎么增长,这和我要解决的问题无关。

  2.我们引入了一个状态 results,并不断变更这个状态。在每次循环的时候,它的值都会发生改变。

  3.当我们的问题稍微改变的时候,比如我要添加一个函数,返回有关 data 长度的一个数组,那么我们需要仔细研读已有的代码,搞清楚整个逻辑,然后新写一个函数(多数情况下,工程师会启用「复制 - 粘贴 - 修改」大法。

  4.这样的代码可读性很差,一旦内部状态超过 10 个,且互相依赖,要读懂它的逻辑并不容易。

  5.这样的代码无法轻易复用。

  如果是函数式编程,你大概会这么写:

  

  这段代码非常简洁、明了,如果你了解filter / map,几乎很难写错。这几乎就是一个通解,一台 machine,有了它,你可以解决任何数据集过滤和映射的问题。当然,你还可以这么抽象:

  

  注意,这两者虽然抽象出来的结果相似,但应用范围是不尽相同的。后者更像一台生产 machine 的 machine(函数返回函数),它将问题进一步解耦。

  这种解耦使得代码不仅泛化(generalization),而且将代码的执行过程分成两阶段,在时序上和接口上也进行了解耦。于是,你可以在上下文A 中调用 extract,在上下文B 中调用 process,产生真正的结果。

  讲到这里我们大致已经能看出函数式编程的一些特点:

  1.提倡组合(composition),组合是王道。

  2.每个函数尽可能完成单一的功能。

  3.屏蔽细节,告诉计算机我要做什么,而不是怎么做。我们看 filter / map,它们并未暴露自身的细节。一个 filter 函数的实现,在单核 CPU 上可能是一个循环,在多核 CPU 上可能是一个 dispatcher 和 aggregator,但我们可以暂时忽略它的实现细节,只需了解它的功能即可。

  4.尽可能不引入或者少引入状态。

  总结

  这些准则不会让你立刻变成一个优秀的工程师,长期奉行他们也并不意味着你能够高枕无忧。

  千里之行,始于足下。

  我们需要时常和同行们进行代码评审,不断优化自己的代码,不要惧怕改善代码质量所需付出的努力。长此以往,你不会觉得看不懂自己半年前写的代码,还将获得同行的赞许,你的程序之路会走的更远!

  嗨

  这里是IMWEB

阅读
分享