The complexity of software applications is growing. The code quality is important in order to make the application stable and easily extensible.软件应用正变的越来越复杂. 要想使得应用变的稳定和易扩展, 代码质量起到至关重要的作用.

Unfortunately almost every developer, including myself, in his career faced with bad quality code. And it’s a swamp.Such code has the following harmful characteristics:不幸的是, 包括我自己在内几乎所有的软件开发者在他的职业生涯中都遇到过质量差的代码, 它带给我们很多的麻烦. 质量差的代码大致有如下几个特点:

Functions are too long and do too many things

Often functions have side effects that are difficult to understand or even debug

Unclear naming of functions and variables

Fragile code: a small modification unexpectedly breaks other application components

It sounds very common: “I don’t understand how this code works”, “this code is a mess”, “it’s hard to modify this code” and the like.经常听到类似这样的抱怨: '我根本不能理解这代码是怎么工作的','这代码太凌乱了','这代码根本无法修改'

Once I had a situation when my colleague quit his job because he dealt with a REST API on Ruby that was hard to maintain. He received this project from previous team of developers.Fixing current bugs creates new ones, adding new features creates a new series of bugs and so on (fragile code). The client didn’t want to rebuild the application with a better design, and the developer made the correct decision to quit.曾经遇到同事停止维护一个应用程序情况, 因为他接手了一个用REST API编写的Ruby应用很难维护. 他从先前的开发团队接手了这个项目. 解决了一个BUG时又产生了另外一个BUG, 当增加新的功能时又引入了一系列新的BUG.客户并不想用一个更好的设计来重新构建应用程序, 开发者也就停止维护了.

Ok, such situations happen often and are sad. But what do to?这种情况经常发生, 让人很悲伤. 针对这种情况我们应该做些什么呢?

The first to keep in mind: simply making the application run and taking care of the code quality are different tasks.首先应该记住: 让程序跑起来和保持良好的代码质量是不同的方面的任务.

On one side you implement the app requirements. But on the other side you should take time to verify if any function doesn’t have too much responsibility, write comprehensive variable and function names, avoid functions with side effects and so on.一方面你需要实现app需求. 另一方面你需要花时间来确保你的函数没拥有太多的职责. 编写易理解的变量和函数名, 避免函数产生副作用等.

The functions (including object methods) are the little gears that make the application run. First you should concentrate on their structure and composition. The current article covers best practices how to write plain, understandable and easy to test functions.函数(包括对象方法)是使应用程序跑起来必不可少的部分.首先, 你应该集中注意力在函数的结构和组合上. 这篇文章介绍了一些最佳实践关于如何编写简单可理解、易测试的函数.

1. Functions should be small. Really small

函数应该尽可能保持简短

Big functions that have a lot of responsibility should be avoided and split into small ones. Big black box functions are difficult to understand, modify and especially test.大函数一般都拥有太多的职责, 应该尽量将大函数拆解. 因为大的黑盒函数很难理解、修改和测试.

Suppose a scenario when a function should return the weight of an array, map or plain JavaScript object. The weight is calculated by summing the property values:现在假设一种场景用来返回数组、map、简单对象的权重. 按以下方式计算权重:

The problem is clearly visible. getCollectionWeight() function is too big and looks like a black box full of surprises.这里问题很明显. getCollectionWeight()这个函数太长, 看起来像一个黑盒充满了令人意想不到的结果.

You probably find it difficult to understand what it does from the first sight. And imagine a bunch of such functions in an application.咋一看你可能发现这个函数非常难理解. 想象一下如果一个应用里面有非常多的这样的函数会怎么样?

When you work with such code, you waste time and effort. On the other side the quality code doesn’t make you feel uncomfortable. Quality code with small and self-explanatory functions is a pleasure to read and easy to follow.当你需要与这样的代码周旋时, 只能是让你的时间和心血白费. 另一方面, 这样差质量的代码会让你觉得很不舒服. 高质量、简洁、能够自解释的函数是非常容易阅读和维护的.

Step 1: Extract weight by type and drop magic numbers

第一步:弃用魔法数字, 按照类型抽取权重计算函数

Now the goal is to split the big function into smaller, independent and reusable ones. The first step is to extract the code that determines the weight of a value by its type. This new function will be named getWeight().现在, 我们的目标是将一长段函数分成简洁、独立和可重用的代码. 第一步是抽取按照类型计算权重的代码. 该函数将被命名为getWeight().

Also take a look at the magic weight numbers: 1, 2 and 4. Simply reading these numbers without knowing the whole story does not provide useful information. Fortunately ES2015 allows to declare const read-only references, so you can easily create constants with meaningful names to knockout the magic numbers.同样, 让我们来看一下魔法数字1、2、4. 如果仅仅单看这些数字, 并不能得到更多有用的信息. 幸运的是, ES2015允许申明只读引用. 因此, 你可以创建富有意义的常量来淘汰那些魔法数字.

Looks better, right?getWeightByType() function is an independent component that simply determines the weight by type. And reusable, as you can execute it in any other function.现在看起来更好了, 对吧? 现在getWeightByType()函数是一个独立的组件, 按照类型计算权重. 函数也要是可重用的, 以便在其他函数中重复使用.

WEIGHT_NULL_UNDEFINED, WEIGHT_PRIMITIVE and WEIGHT_OBJECT_FUNCTION are self-explanatory constants that describe the type weights. You don’t have to guess what 1, 2 and 4 numbers mean.WEIGHT_NULL_UNDEFINED、WEIGHT_PRIMITIVE以及WEIGHT_OBJECT_FUNCTION都是含义非常明确能描述权重的常量, 不用猜1,2,4到底是什么意思了.

Step 2: Continue splitting and make it extensible

第二步:继续分解和扩展函数

However the updated version still has drawbacks.Imagine that you have the plan to implement the weight evaluation of a Set or even other custom collection. getCollectionWeight() will grow fast in size, because it contains the logic of collecting the values.经过改造的函数仍然是有缺点的. 想象一下你需要实现其他Set, 甚至是自定义集合的权重计算. getCollectionWeight()这个函数将会增长非常快, 因为它包含收集值的逻辑.

Let’s extract into separated functions the code that gathers values from maps getMapValues() and plain JavaScript objects getPlainObjectValues(). Take a look at the improved version:让我们为收集值抽取出单独的函数, maps抽取成getMapValues(), 简单对象抽取成getPlainObjectValues(). 接下来让我们看看简化后的版本:

Then getCollectionWeight() would become truly plain, because the only thing it needs to do is: get the collection values getCollectionValues() and apply the sum reducer on it.现在getCollectionWeight()该函数变得真正简洁了, 它所需要做的唯一事就是:获取集合值然后在它上面运用reducer函数.

You can also create a separated reducer function:你也可以创建一个单独的reducer函数:

*getCollectionWeight() function is now protected from fast growth if you plan to implement the weight calculation of other collection types

*The extracted functions are now decoupled and reusable components. Your colleague may ask you to import these nice functions into another project: and you can easily do that

*If accidentally a function generates an error, the call stack will be more precise because it contains the function names. Almost instantly you could determine the function that makes problems

*The split functions are much easier to test and reach a high level of code coverage. Instead of testing one big function with all possible scenarios, you can structure your tests and verify each small function separately

These advantages help you survive in the complexity of the applications.这些优点能够帮助你摆脱复杂应用程序的困境

As a general rule, your functions should not be longer than 20 lines of code. Smaller - better.一般来说你的函数不应该超过20行. 越小越好.

I think now you want to ask me a reasonable question: “I don’t want to create functions for each line of code. Is there a criteria when I should stop splitting?” This is a subject of the next chapter.现在, 我想你可能要问我一个问题了:'我并不想为每一行代码都创建一个函数, 抽离函数是否有一个标准我们应该遵循.'这是下一章节的主题

2. Functions should be plain

2. 函数应该是清晰的

Let’s relax a bit and think what is actually a software application?让我们停下来放松, 思考下软件应用的本质是什么?

Every application is implementing a list of requirements. The role of developer is to divide these requirements into small executable components (namespaces, classes, functions, code blocks) that do a well determined task.每一个应用程序都是为了实现一系列的需求. 开发者的角色是把这些需求分割成小的可执行的单元(命令空间、类、函数、代码块)能给很好的执行任务.

A component consists of other smaller components. If you want to code a component, you need to create it from components at only one level down in abstraction.一个组件又又另一系列小的组件组成.当你想要编码创建一个组件时, 你需要在同一个抽象级别去抽离

In other words, what you need is to decompose a function into smaller steps, but keep these steps at the same, one step down, level of abstraction. This is important because it makes the function plain and implies to “do one thing and do it well”.换句话说, 你所需要做的就是将一个函数解耦分离成若干小步骤, 同时需要保持这些小的函数在同一抽象层次上.这很重要, 因为它能使函数更简单, 同时也表达出'做一件事并且把它做好'的理念

Why is this necessary? Because plain functions are obvious. Obvious means easy to read and modify.为什么这是必须的?因为简洁的函数阅读起来非常清晰, 清晰意味着易读和易修改

What are steps at one level down in abstraction to implement the function getOnlyPrime()? Let’s formulate this way:在同一个层次上抽象这个函数的步骤是什么?让我们来公理化该这个步骤

1

To implement getOnlyPrime() function, filter the array of numbers using isPrime() function.

1

为了实现getOnlyPrime()函数, 使用isPrime()来过滤该函数.

Simply, just apply a filter function isPrime() over the array of numbers.简单地, 在数组上运用isPrimse()函数即可

Do you need to implement the details of isPrime() at this level? No, because getOnlyPrime() function would have steps from different level of abstractions. The function would take too much responsibility.你需要在getOnlyPrime()这个函数级别上实现isPrime()的详情嘛? 不必的, 因为getOnlyPrime()函数还拥有其它层次上的抽象.该函数将会拥有太多职责

Having the plain idea in mind, let’s implement the body of getOnlyPrime() function:在头脑中时刻保持简洁的想法, 接下来让我们来实现getOnlyPrimse()函数体

getOnlyPrime() is small and plain. It has only strictly necessary steps from one level down in abstraction.现在getOnlyPrime()函数很清晰短小. 它仅仅包含在一个实现层次上的抽象步骤

The readability of complex functions can be much improved if you follow the rule of making them plain. Having each level of abstraction coded precisely prevents the creation of big chunks of unmaintainable code.遵循使函数清晰的规则能够大大改善函数的可读性.确保在每一个抽象级别上编写代码能够阻止不可维护代码的产生

3. Use concise function names

使用简洁的函数名字

Function names should be concise: no more and no less. Ideally the name suggests clearly what the function does, without the necessity to dive into the implementation details.函数名字应该保持简洁: 恰如其分的命名. 理想情况下, 函数名字应该能够清晰的表达出函数的功能, 而不用表达函数的实现细节

For function names use camel case format that starts with a lowercase letter: addItem(), saveToStore() or getFirstName().使用小写字母开头的驼峰命名法来命名函数.

Because functions are actions, the name should contain at least one verb. For example deletePage(), verifyCredentials(). To get or set a property, use the standard set and get prefixes: getLastName() or setLastName().因为函数是执行的一个动作, 函数名字应该至少有一个动词. 为了设置一个属性, 使用标准的get, set前缀

Avoid in the production code misleading names like foo(), bar(), a(), fun(), etc. Such names have no meaning.避免在生产环境中使用容易误导人的名字例如foo(), bar(), a()等等. 这些名字毫无意义.

If functions are small and plain, names are concise: the code is read as a wonderful prose.如果函数名字清晰简短, 名字简洁, 代码将能够像阅读散文一样随心自如

4. Conclusion

总结

Certainly the provided examples are quite simple. Real world applications are more complex. You may complain that writing plain functions, with only one level down in abstraction, is a tedious task. But it’s not that complicated if your practice right from the start of the project.虽然上面的例子非常简单. 实际的例子是非常复杂的.你可能抱怨编写在一个抽象级别, 清晰的函数是一个沉闷的任务. 但是如果你从工程开始就遵循这样的实践, 编写这样的函数并不会太复杂.

If an application already has functions with too much responsibility, you may find hard to reorganize the code. And in many cases impossible to do in a reasonable amount of time. At least start with small: extract something you can.如果一个应用程序的函数拥有太多的职责, 你可能发现很难组织你的代码. 在大多数情况下这不可能在一个合理的时间去完成这样的工作. 至少以编写清晰简短的函数开始.

Of course the correct solution is to implement the application correctly from the start. And dedicate time not only to implementation, but also to a correct structure of your functions: as suggested make them small and plain.当然, 正确的解决方案是要从一开始就正确的的遵循本文的方式去实现应用. 不仅要花时间去实现函数功能, 也要花时间在如何正确的编写小而清晰的函数上面.

Measure seven times, cut once.

ES2015 implements a nice module system, that clearly suggest that small functions are a good practice.ES2015实现了一个模块系统, 清晰的表达出小函数是一个好的实践.

Just remember that clean and organized code always deserves investing time. You may find it hard to do. You may need a lot of practice. You may come back and modify a function multiple times.记住简洁和可重新组织的代码是值得花费精力的.你可能发现这很难.需要很多的实践.可能需要多次频繁的修改一个函数