软件
软件是可以改变的。这就是为什么它被称为「软」件,它的可塑性是比硬件强的。一个优秀的工程师团队应该是一个公司一笔惊人的财富,他们编写可以随着业务发展而不断增值的系统。
那么,为什么我们在这方面做的如此糟糕呢?你听说过多少完全失败的项目?或者成为「遗产」,必须完全重写 (重写通常也会失败! )
软件系统是如何“失败”的呢?难道不能在它正确之前进行修改吗?这就是我们的承诺!
很多人选择用 Go 来构建系统,因为它已经做出了许多选择,人们希望这些选择能让它更经得起遗产的考验。【相关推荐:Go视频教程】
和我之前 Scala 生涯相比 我形容它简直会让你有上吊的冲动,Go 只有25个关键词和 很多 可以从标准库和一些其他小型库中构建的系统。 愿景是通过 Go 你可以编写代码并在6个月内回顾它,它仍然有意义。
1.与大多数替代品相比,测试,基准测试,语义解析和装载方面的工具是一流的。
2.很棒的标准库。
3.严谨的反馈回路让编译非常迅速
4.Go 有向后兼容性承诺。看起来 Go 将来会获得泛型和其他功能,但设计师们已经承诺,即使你 5 年前写的 Go 代码仍然会构建。我花了几周的时间将项目从Scala 2.8 升级到 2.10。
即使拥有所有这些优秀的属性,我们仍然可能会制造出糟糕的系统,因此我们应不论你使用的语言优秀与否,你都应该回顾过去的软件工程并理解其中获得的经验教训。
1974年,一位名叫 曼尼·雷曼 的聪明的软件工程师写下了 雷曼软件进化定律。
[scode]这些定律描述了推动新发展的力量和阻碍进步的力量之间的平衡。[/scode]
如果我们不希望开发的系统变成遗产,被一遍又一遍的重写,那么这些力量是我们需要着重理解的。
连续变化规律
[scode]实际生活中被使用的软件系统都必须不断的改变,要不然就会被大环境淘汰[/scode]
很明显,一个系统 必须 不断的改变,要不然就会变的越来越没用,但是这种情况为什么经常被忽略呢?
因为很多开发团队在指定的日期交付一个项目会得到奖金,然后他们会继续开发下一个项目。如果这个软件是「幸运的」,至少它会以某种形式移交给另一组人来维护它,但是他们一定不会继续迭代它。
人们通常关心的是选择一个框架来帮助他们「快速交付」,而不是关注系统持久性发展。
即使你是一名出色的软件工程师,你仍然会因为不了解自己系统的未来需求而成为受害者。随着业务的变化,你写的出色的代码也会变得不再适用。
雷曼在70年代很成功,因为他给了我们另一条值得深思的规律。
复杂性增加的规律
[scode]随着系统的发展,除非采取措施减少系统复杂性的增加,否则系统的复杂程度会持续增加[/scode]
他现在要所说的就是:我们不能让软件团队成为纯粹的功能工厂,只是通过将越来越多的功能集中到软件上, 来让系统能够继续长期运行。
随着我们知识领域的变化,我们 必须 持续管理系统的复杂性。
重构
软件的开发可以在 许多 方面保持软件的可塑性,例如:
开发人员授权
通常「好」的代码。关注代码合理的分离,等等
沟通能力
体系结构
可观性
可部署性
自动化测试
闭环
我将重点放在重构上。在开发人员编程的第一天经常听到的话就是「我们需要重构它」。
这句话从和而来?重构与编写代码有什么不同?
我知道我和其他很多人都 认为 我们在进行重构,但我们错了。
马丁·福勒描述了人们是如何犯错的
[scode]然而「重构」经常被用在不合适的地方。如果有人讨论一个系统在重构时出现了几天故障,你可以肯定他们不是在重构。[/scode]
那是什么呢?
因式分解
在学校学习数学时,你可能学了因式分解。这里有一个非常简单的例子
计算 1/2 + 1/4
为此,将分母 分解,将表达式转换为
2/4 + 1/4 你可以把它变成 3/4.
我们可以从中吸取一些重要的教训。当我们 分解表达式 时,我们没有改变表达式的含义。两者都等于 3/4,但我们通过将 1/2 变为 2/4 后,我们的工作变得更容易了;它更适合我们的「领域」。
当你重构代码时,你应该在「符合」你当前系统需求的情况下,尝试找到一个方法来使你的代码更容易理解。关键是你不应该改变代码原有的行为.
Go 的一个例子
下面这个方法是用特定的 language 问候 name
func Hello(name, language string) string {
if language == "es" {
return "Hola, " + name
}
if language == "fr" {
return "Bonjour, " + name
}
// 想象一下更多的语言
return "Hello, " + name
}
如果有几十个 if
语句会让人感觉不舒服,而且我们还要重复的使用特定的 language
去伴随着 , 问候 name
。因此,我们来重构代码。
func Hello(name, language string) string {
return fmt.Sprintf(
"%s, %s",
greeting(language),
name,
)
}
var greetings = map[string]string {
es: "Hola",
fr: "Bonjour",
//等等...
}
func greeting(language string) string {
greeting, exists := greetings[language]
if exists {
return greeting
}
return "Hello"
}
实际上,这个重构的本质并不重要,重要的是我们没有改变代码的行为。
当重构时,你可以做任何你喜欢的事情,添加接口,新类型,函数,方法等等。唯一的规则是你不能改变代码的行为。
重构代码时不要改变功能
这非常重要。如果你重构时改变功能,你相当于同时在做 两 件事。作为软件工程师,我们应该学习把系统分成不同的文件/包/功能/等等,因为我们知道试图理解一大块东西是困难的。
我们不要一次想很多事情,因为那会使我们犯错误。我目睹了许多重构工作的失败,因为开发人员贪多嚼不烂。
当我在数学课上用笔和纸做因式分解时,我必须手动检查我是否改变了头脑中表达式的意思。当我们重构代码时,尤其是在一个重要的系统上,我们如何知道我们是否改变了功能?
那些选择不编写测试的人通常依赖于手动测试。除非是一个小项目,要不然这将是一个巨大的时间消耗,并且从长远来看不利于系统将来的扩展。
.为了安全地重构,您需要单元测试因为它提供了
.可以在不担心功能改变的情况下重构代码
.有助于开发人员编写关于系统应该如何运行的文档
.比手工测试更快更可靠的反馈
暂无评论内容