资讯

易奇官网-活动

易奇科技软件人—软件开发的本能,30年程序猿的混乱笔记之一

日期:12-04

“千帆过尽仍少年”,对于软件工程师/程序猿来说,保留技术初心、不断提升实力是夯实自己的不二法则。而本文的作者,作为一名有着近三十多年开发经验的“老”行内人,就在本文中详细总结了自己这些年踩过的坑和实践得出的真理,谈到了包括软件开发、软件开发的团队协同、软件工程师的个人成长等。相信能帮助你成为更优秀的软件工程师或者更了解我们这些程序猿。



下面是我 30 年来从事软件开发过程中所学到的一些实际经验,可能有些听起来悲凉,但都是我的切身经验之谈。

再次强调,有些内容真的是愤世嫉俗,有些则是对不同工作岗位的长期“领悟”。

软件开发

先明确问题,再开始写代码

如果你不知道你想要解决的问题是什么,那你肯定就不知道要写些什么代码。在编写任何代码之前,先明确地把应用程序是如何工作的写下来。

    “如果没有需求或设计,编程就是向空文本文件不断增加bug的艺术。”——Louis Srygley

有时,即使只是“电梯演讲”(指短时间内表述结果内容)那么长——用仅仅两个自然段来描述这个应用程序的功能——也足够了。

有时候我看着自己的软件代码发呆,不知道下一步该怎么做,但其实只是因为下一步本来就还没有被定义出来。出现这种情况,就意味着是时候停下来,与团队的兄弟们讨论一下了——或者重新考虑解决方案。

将步骤写为注释

如果你不知道如何开始,请先用自然语言、或者母语简短的描述软件流程,然后用代码填充注释之间的空白。比这更好的做法是:将每个注释视为一个函数,然后编写出能完全实现其功能的代码。

Gherkin是帮助你了解期望(expectation)的好帮手

Gherkin是一种测试描述格式,它指出“鉴于系统处于特定状态,当发生某些事情时,这是预期的后果”。即使你不使用任何能读取Gherkin的测试工具,它也会让你很好地理解应用程序的预期效果。

单元测试很好,集成测试更好

在我目前的工作中,我们只测试软件模块和类(例如,我们只为视图层编写测试,然后仅测试控制器层,依此类推)。它能让我们了解到某一部分有没有出错,但缺乏对整体的观察——而集成测试测试了整个系统的行为,在这方面会表现得更好。

当然,测试可以让API更好

我们在不同层次中编码:有一个存储层,应该使我们的数据永久存储;有一个处理层,应该对存储的数据进行一些转换;有一个视图层,它有关于数据必须如何被展示出来的信息......等等。

正如我所提到的,集成测试感觉更好,但是单独测试不同层可以让你更好地了解其API。然后你可以更好地了解如何调用东西:API是否太复杂了?是否需要保留大量数据才能进行一次调用?

时刻准备好扔掉你的代码

很多人在刚开始使用TDD(测试驱动开发,Test-Driven Development)时,一旦被告知他们可能不得不重写很多东西,就会变得很生气。

TDD“旨在”扔掉代码:越了解你的问题,那么你就会越明白,无论你写了什么,从长远来看都无法解决问题。

所以你不应该担心这个。你的代码不是一面墙:如果你必须永远抛弃它,那也不是白白浪费了材料。当然这意味着你编写代码的时间一去不复返了,但是你现在对这个问题有了更好的理解。

好的语言生来带有综合测试

可以肯定的是,如果一种语言在其标准库中自带一个测试框架——即使小得不能再小——那么与没有测试框架的语言相比,它周围的生态系统仍将拥有更好的测试,无论该语言的外部测试框架有多好。

未来思路是垃圾思路

当软件开发人员试图解决问题时,他们有时会试图找到一种方法来一下解决所有问题,包括未来可能出现的问题。

但现实就是这样:未来的问题永远不会到来,你最终要么必须维护一堆永远不会被完全使用的庞大代码,要么得整个重新写,因为有一大堆屁用没有的东西......

解决你现在遇到的问题,然后解决下一个,然后再下一个。直到有一天,你会发现这些软件开发解决方案中显现出了一种固定的模式,然后你才能真正地“一次性解决所有问题”。

技术文档是写给未来自己的情书

我们都知道,为函数、类(class)和模块编写该死的文档是一个痛苦的过程。但是以后当你看到文档就能回想起来当时你编写函数时的思路,你就会明白将来文档能在关键时刻救你一命。

功能文档是份合同

当你以编写文档作为自己编程工作的起始点时,你实际上是在签订合同(可能是跟未来的自己):我说了这个函数要做这件事情,那么它就必须做这件事情。如果稍后你发现代码与文档不匹配,那你就是代码出了问题,而不是文档出了问题。

如果一个函数的描述包含“和”,这就是不对的

一个函数应该且仅应该做一件事,真的。当你编写函数文档并发现你写了“和”这个字的时候,这意味着该函数不仅仅是做一件事。那么就需要将该函数分解为两个独立函数并删除“和”。

不要使用布尔型变量作为参数

当你设计一个函数时,你可能会想要添加一个flag——不要这样做。

现在,让我给你举个栗子:假设你有一个消息传递系统,并且有一个函数可以将所有消息返回给用户,称为getUserMessages。但有一种情况是需要返回每条消息的摘要(例如,第一段)或完整消息,因此,你添加一个名为retrieveFullMessage的flag/布尔参数。

再说一次,不要这样做。

因为任何读你代码的人都会看到getUserMessage(userId,true)并想知道这里的true到底是个什么意思。

你可以将函数重命名为getUserMessageSummaries并使用另一个getUserMessagesFull或类似的东西,但每个函数只调用原始的getUserMessage为true或false——但是类/模块外部的接口仍然是清晰的。

但是一定“不要”在函数中添加flags / Boolean作为参数。

注意界面的变化

在上面几点中,我提到了重新命名函数的问题,如果你能控制使用该函数的整个源头,那就不算是问题,只需要搜索和替换即可。

但是,如果该函数实际上是由库公开的,那么你不能随便地更改函数名称。这将打破你无法控制的许多其他应用程序,并惹恼其他人。

你可以通过文档或某些代码功能创建新函数并将当前函数标记为已弃用,然后,经过几次释放后,你终于可以Kill掉原来的函数了。

    (你可以做的一个有些混蛋的举动是创建新函数,将当前函数标记为已弃用,并在函数开头添加一个休眠,这样一来使用旧函数的人会被迫更新。)

好的语言自带集成的文档

如果语言有自己的方式来记录函数、类、模块或其他,而且带有一个哪怕最简单的文档生成器,你就可以确切知道所有的函数、类、模块、库、框架都具有良好的文档了(不是说一定特别好,但至少是比较好的)。

大多数情况下,没有集成文档的语言,文档方面做得都不怎么样。

一门语言绝不仅仅是一门语言而已,我天然推荐.net

编程语言就是你写的、而且能做事情的东西,但在特殊关键词以外它还有很多别的东西:它有一个构建系统,它有一个依赖控制系统,它有一种使工具/库/框架互动的方式,它有一个社区,它有一种与人打交道的方式。

不要仅仅因为一种语言容易使用就选择它。永远记住,你可能因为一种语言的语法很简明而支持这种语言,但是与此同时你也是在支持维护人员对待这个社区的方式。

有时候,宁愿让应用程序崩溃也不要什么都不做

虽然这听起来很奇怪,但即使在处理过程中添加了某些错误,也不要默默地捕捉到错误但什么都不做。

Java中一个可悲的常见模式是:

这看起来跟处理异常没有什么关系——除了重复了一遍,仅此而已。

如果你不知道如何处理它,那就随它去吧,你早晚会知道它会发生什么。

如果你知道如何处理该问题,那么就处理它

与前一点相反:如果你知道什么东西在何时会导致异常/错误/某种结果,并且知道如何处理它,那么就请处理它吧。显示错误信息,尝试将数据保存在其他位置,将日志文件中用户的输入捕获到以便以后处理,但要记得处理它。

类型决定你的数据是个什么东西

内存中只是一串字节序列;字节只是 0 到 255 之间的数字组合;这些数字的真正含义取决于语言的类型系统。

例如,在C中,值为 65 的char型变量可能是字母“A”,值为 65 的int型变量是数字65——处理数据时请不要忘记这一点。这也是为什么大多数人在用布尔型变量做加法以查看True的数量时经常出错。

现在,让我展示一下我最近看到的一个JavaScript里的例子:


如果你的数据具有模式(schema),请使用结构(structure)来保留它

你可能会想要使用列表(或元组,如果你用的语言允许的话)来保存数据,如果它很简单——比如说,只有 2 个字段。

但是如果你的数据有一个模式(schema),有一个固定的格式——你应该每次都使用一些结构来保存它,不管是用struct还是class。

理解并保持cargo cult的方式

“Cargo cult”是一种理念,如果其他人那样做了,那么我们也可以。大多数情况下,cargo cult只是对一个问题的偷懒的解决方法:

    “如果X这样做了,我们为什么要考虑如何正确存储我们的用户数据?”

    “如果有某巨头公司是这样存储数据的,那么我们也可以”。

    “如果有某巨头公司支持这种做法,那就说明这种方法很好。”

    ......

不要管所谓的“合适的生产力工具”,你只需要尽力去push进程

“合适的生产力工具”其实意味着:对于某件事情,有一个正确的工具和一个错误的工具——例如,应该使用另外的某种语言/框架而不是当前的语言/框架。但每当我听到有人提到这个词时,他们都是在试图推销他们喜欢的语言/框架,而不是合适的语言/框架。

“正确的工具”比你想象的更明显

也许你当前的项目需要处理一些文本,也许你很想说“让我们用Perl吧!”,因为你知道Perl在处理文本时非常强大。

但你漏掉了哪一点呢?你在一个C的团队工作,每个人会C,而不是Perl。

当然,如果它是一个小的、“放在角落”的不起眼的项目,那么用Perl就可以了——但如果它对公司很重要,那么最好还是用.net。

    PS:你的英雄项目(本文稍后将详细介绍)可能因此而失败。