乐趣区

关于程序员:如何写出让同事好维护的代码

简介: 写出整洁的代码,是每个程序员的谋求。《clean code》指出,要想写出好的代码,首先得晓得什么是恶浊代码、什么是整洁代码;而后通过大量的刻意练习,能力真正写出整洁的代码。

写出整洁的代码,是每个程序员的谋求。《clean code》指出,要想写出好的代码,首先得晓得什么是恶浊代码、什么是整洁代码;而后通过大量的刻意练习,能力真正写出整洁的代码。

WTF/min 是掂量代码品质的唯一标准,Uncle Bob 在书中称蹩脚的代码为沼泽(wading),这只突出了咱们是蹩脚代码的受害者。国内有一个更适宜的词汇:屎山,尽管不是很斯文然而更加主观,程序员既是受害者也是加害者。

对于什么是整洁的代码,书中给出了巨匠们的总结:

  1. Bjarne Stroustrup:优雅且高效;含糊其辞;缩小依赖;只做好一件事
  2. Grady booch:简略间接
  3. Dave thomas:可读,可保护,单元测试
  4. Ron Jeffries:不要反复、繁多职责,表达力(Expressiveness)
    其中,我最喜爱的是表达力(Expressiveness)这个形容,这个词仿佛道出了好代码的真谛:用简略间接的形式描绘出代码的性能,不多也不少。

命名的艺术

坦率的说,命名是一件艰难的事件,要想出一个恰到好处的命名须要一番功夫,尤其咱们的母语还不是编程语言所通用的英语。不过这一切都是值得了,好的命名让你的代码更直观,更有表达力。

好的命名应该有上面的特色:

货真价实

好的变量名通知你:是什么货色,为什么存在,该怎么应用

如果须要通过正文来解释变量,那么就先得不那么货真价实了。

防止误导

不要挂羊头卖狗肉

不要笼罩习用缩略语

这里不得不吐槽前两天才看到的一份代码,竟然应用了 l 作为变量名;而且,user 竟然是一个 list(单复数都没学好!!)

有意义的辨别

代码是写给机器执行,也是给人浏览的,所以概念肯定要有区分度。

  1. 应用读的进去的单词

如果名称读不进去,那么探讨的时候就会像个傻鸟

  1. 使用方便搜寻的命名

名字长短应与其作用域大小绝对应

  1. 防止思维映射

比方在代码中写一个 temp,那么读者就得每次看到这个单词的时候翻译成其真正的意义

正文

有表达力的代码是无需正文的。

The proper use of comments is to compensate for our failure to express ourself in code.

正文的适当作用在于补救咱们用代码表白用意时遇到的失败,这听起来让人丧气,但事实的确如此。The truth is in the code, 正文只是二手信息,二者的不同步或者不等价是正文的最大问题。

书中给出了一个十分形象的例子来展现:用代码来论述,而非正文

bad
// check to see if the employee is eligible for full benefit
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))

good
if (employee.isEligibleForFullBenefits())

因而,当想要增加正文的时候,能够想想是否能够通过批改命名,或者批改函数(代码)的形象层级来展现代码的用意。

当然,也不能因噎废食,书中指出了以下一些状况属于好的正文

  1. 法务信息
  2. 对用意的正文,为什么要这么做
  3. 警示
  4. TODO 正文
  5. 放大看似不合理之物的重要性

其中集体最同意的是第 2 点和第 5 点,做什么很容易通过命名表白,但为什么要这么做则并不直观,特地波及到专业知识、算法的时候。

另外,有些第一感觉“不那么优雅”的代码,兴许有其非凡违心,那么这样的代码就应该加上正文,阐明为什么要这样,比方为了晋升要害门路的性能,可能会就义局部代码的可读性。7 点倡议助您写出优雅的 Java 代码!这篇也能够看下。

最坏的正文就是过期或者谬误的正文,这对于代码的维护者(兴许就是几个月后的本人)是微小的挫伤,惋惜除了 code review,并没有简单易行的办法来保障代码与正文的同步。

函数

函数的繁多职责

一个函数应该只做一件事,这件事应该能通过函数名就能清晰的展现。判断办法很简略:看看函数是否还能再拆出一个函数。

函数要么做什么 do_sth, 要么查问什么 query_sth。最恶心的就是函数名示意只会 query_sth, 但事实上却会 do_sth, 这使得函数产生了副作用。比方书中的例子

public class UserValidator {
    private Cryptographer cryptographer;
    public boolean checkPassword(String userName, String password) {User user = UserGateway.findByName(userName);
        if (user != User.NULL) {String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {Session.initialize();
                return true;
            }
        }
        return false;
    }
}

函数的形象层级

每个函数一个抽象层次,函数中的语句都要在同一个形象层级,不同的形象层级不能放在一起。比方咱们想把大象放进冰箱,应该是这个样子的:

def pushElephantIntoRefrige():
    openRefrige()
    pushElephant()
    closeRefrige()

函数外面的三句代码在同一个层级(高度)形容了要实现把大象放进冰箱这件事程序相干的三个步骤。显然,pushElephant 这个步骤又可能蕴含很多子步骤,然而在 pushElephantIntoRefrige 这个层级,是无需晓得太多细节的。

当咱们想通过浏览代码的形式来理解一个新的我的项目时,个别都是采取广度优先的策略,自上而下的浏览代码,先理解整体构造,而后再深刻感兴趣的细节。

如果没有对实现细节进行良好的形象(并凝练出一个货真价实的函数),那么阅读者就容易迷失在细节的汪洋里。

某种程度看来,这个跟金字塔原理也很像

每一个层级都是为了论证其上一层级的观点,同时也须要下一层级的反对;同一层级之间的多个论点又须要以某种逻辑关系排序。

pushElephantIntoRefrige 就是核心论点,须要多个子步骤的反对,同时这些子步骤之间也有逻辑先后顺序。

函数参数

函数的参数越多,组合出的输出状况就愈多,须要的测试用例也就越多,也就越容易出问题。

输入参数相比返回值难以了解,这点深有同感,输入参数切实是很不直观。从函数调用者的角度,一眼就能看出返回值,而很难辨认输入参数。输入参数通常逼迫调用者去查看函数签名,这个切实不敌对。

向函数传入 Boolean(书中称之为 Flag Argument)通常不是好主见。尤其是传入 True or False 后的行为并不是一件事件的两面,而是两件不同的事件时。这很显著违反了函数的繁多职责束缚,解决办法很简略,那就是用两个函数。

Dont repear yourself

在函数这个层级,是最容易、最直观实现复用的,很多 IDE 也难帮忙咱们讲一段代码重构出一个函数。

不过在实践中,也会呈现这样一种状况:一段代码在多个办法中都有应用,然而又不齐全一样,如果形象成一个通用函数,那么就须要加参数、加 if else 区别。这样就有点难堪,貌似能够重构,但又不是很完满。

造成上述问题的某种状况是因为,这段代码也违反了繁多职责准则,做了不只一件事件,这才导致不好复用,解决办法是进行办法的细分,能力更好复用。也能够思考 template method 来解决差别的局部。

测试

十分羞愧的是,在我经验的我的项目中,测试(尤其是单元测试)始终都没有失去足够的器重,也没有试行过 TDD。正因为缺失,才更感良好测试的宝贵。

咱们常说,好的代码须要有可读性、可维护性、可扩展性,好的代码、架构须要不停的重构、迭代,但自动化测试是保障这所有的根底,没有高覆盖率的、自动化的单元测试、回归测试,谁都不敢去批改代码,只能任其腐烂。

即便针对外围模块写了单元测试,个别也很随便,认为这只是测试代码,配不上生产代码的位置,认为只有能跑通就行了。这就导致测试代码的可读性、可维护性十分差,而后导致测试代码很难追随生产代码一起更新、演变,最初导致测试代码生效。所以说,脏测试 – 等同于 – 没测试。

因而,测试代码的三要素:可读性,可读性,可读性。

对于测试的准则、准则如下:

  1. You are not allowed to write any production code unless it is to make a failing unit test pass. 没有测试之前不要写任何性能代码
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. 只编写恰好可能体现一个失败状况的测试代码
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test. 只编写恰好能通过测试的性能代码

测试的 FIRST 准则:

  1. 疾速(Fast)测试应该够快,尽量自动化。
  2. 独立(Independent)测试应该应该独立。不要相互依赖
  3. 可反复(Repeatable)测试应该在任何环境上都能反复通过。
  4. 自我验证(Self-Validating)测试应该有 bool 输入。不要通过查看日志这种低效率形式来判断测试是否通过
  5. 及时(Timely)测试应该及时编写,在其对应的生产代码之前编写

本文作者:xybaby
本文起源:“掘金”

退出移动版