8条软件工程原则的生存之道
软件工程原则是由一些著名的软件工程师和作者在我们的行业中介绍的方法、风格、哲学和最佳实践的清单。
在这篇文章中,我将介绍8条软件工程原则,它们将帮助你开发高质量的软件。
KISS (Keep It Simple, Stupid)
在我列出的最重要的软件工程原则中,第一个是KISS。它是"Keep It Simple, Stupid "的缩写。
当软件系统保持简单时,它们的工作效果最好。避免不必要的复杂性将使你的系统更强大,更容易理解,更容易推理,更容易扩展。
这是很明显的。但是,我们这些工程师,常常倾向于把事情复杂化。我们使用那些没有人知道的花哨的语言功能,并感到自豪。我们在项目中为每一件简单的事情引入无数的依赖关系,最终陷入依赖关系的地狱。我们为每一件简单的事情创建了无尽的微服务。
@phillip_webb的这条推文讽刺地总结了我们在微服务方面的做法有点过火。
请记住,每当你在你的项目中添加一个新的依赖,或开始使用那个花哨的新框架,或创建一个新的微服务,你都会给你的系统引入额外的复杂性。你需要考虑这种复杂性是否值得。
获奖作家Venkat Subramaniam在一次会议上做了这样的演讲,题目是:不要从复杂性中走开,要跑!要跑!要跑!。
这个演讲是如此的贴切。它解释了为什么你应该尽可能地避免复杂化。
DRY(不要重复自己的工作)
DRY原则旨在减少软件系统中代码的重复和努力。
它基本上意味着,你不应该在多个地方写相同的代码/配置。如果你这样做了,那么你就必须让它们保持同步;而且一个地方的代码的任何改变也需要在其他地方进行改变。
以下摘录自《务实的程序员》一书,用一句话概括了这一点。
每一个知识点在系统中都必须有一个单一的、明确的、权威的表述《实用主义程序员》 DRY原则促进了重用性。它使代码更可维护,更可扩展,更少的错误。
以下是软件工程中基于DRY原则的一些概念。
-
继承和组合
继承和组合都允许你在一个地方写代码,然后在其他地方重用它。
-
数据库规范化
数据库规范化是一种用于数据库的设计技术,以消除数据的冗余(重复)。
YAGNI (You Aren't Gonna Need It)
和KISS原则一样,YAGNI的目的也是为了避免复杂性,特别是那种由于增加你认为将来可能需要的功能而产生的复杂性。
它指出,你不应该为了解决未来的问题而引入一些你现在没有的东西。始终在你真正需要的时候才去实现这些东西。这将帮助你保持你的软件精简和简单。这也将为你节省额外的金钱和精力。
此外,你可能认为你在未来需要这个功能。但很多时候,由于我们的软件世界的要求不断变化,你甚至可能不需要它。
记住,"过早的优化是万恶之源" -Donald knuth
SOLID原则
SOLID是一个包含5个软件工程原则的列表。它是一个元缩写,每个字母都对应着另一个缩写。
- S- SRP (单一责任原则)
- O- OCP (开放封闭原则)
- L- LSP (利斯科夫替代原则)
- I- ISP (界面隔离原则)
- D- DIP (依赖反转原则)
让我们逐一了解上述软件工程原则。
1.SRP (单一责任原则)
单一责任原则指出,每个函数、类、模块或服务都应该有一个明确定义的责任。换句话说,一个类/功能/模块应该有 一个且仅有一个原因以改变。
但为什么这很重要呢?好吧,当你以这样的方式定义你的函数或类,使它们专注于并负责一个单一的功能时,你的代码就变得更容易理解、维护和修改。每当你想对一个功能进行任何修改时,你会清楚地知道你必须在那个单一的DRY地方修改负责该功能的代码。
让我们通过一些现实世界的类比来理解单一责任原则。
-
在一个房子里,厨房是用来做饭的,卧室是用来睡觉的。两者都有一个明确定义的责任。你不会在卧室做饭,也不会在厨房睡觉。当你想吃东西时,你去厨房;当你感到困倦时,你去卧室。这使事情变得如此方便。
-
在一个公司里,项目经理、工程师、人力资源部门、销售人员和其他所有人都有明确的责任。项目经理收集需求并跟踪项目的进展。工程师们写代码。销售人员负责营销和销售产品。
SRP原则使代码更有条理。它提高了代码的可读性。它也对可重用性有很大的贡献。如果你有简短而集中的函数/类,你就能轻易地重用它们。但如果你有一个做了太多事情的单一函数,那么如果你只需要该函数实现的一小部分功能,你就无法使用它。
2.OCP(开放/封闭原则)
当我们开发软件时,我们分阶段进行。我们实现一堆功能,对其进行测试,然后将其发布给用户。然后我们开始实现下一组功能。
当我们开发新的功能时,我们最不想做的事情就是对现有的功能进行修改,因为这些功能是有效的,而且经过了测试。相反,我们试图在现有功能的基础上建立新的功能。
开放/封闭原则是上述想法的促进者。它建议我们在构建我们的函数/类/模块时,应该使它们对扩展开放,但对修改关闭。
-
对扩展开放
我们应该能够在不破坏现有代码的情况下向类/模块添加新的功能。这可以通过继承和组合来实现。
-
不允许修改
我们应该努力避免对现有功能进行破坏性的修改,因为这将迫使我们重构大量的现有代码,并编写一大堆测试,以确保这些修改能够发挥作用。
3.LSP(利斯科夫替代原则)
Liskov替代原则简单地说就是每个子类/派生类都应该可以替代它们的父类/基类而不改变程序的正确性。换句话说,你的子类的对象应该和你的超类的对象表现得一样。
让我们通过一个例子来理解这一点。假设你有一个叫做Bird 的类和它的一个子类,叫做Penguin 。
class Bird {
public void fly() {
System.out.println("Bird flying...");
}
public void eat() {
System.out.println("Bird eating...");
}
}
class Penguin extends Bird {
public void fly() {
throw new UnsupportedOperationException("Can't fly.");
}
}
class LSPTest {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly();
}
}
根据Liskov替代原则,如果你有一段使用Bird 对象的代码,那么你应该可以用Penguin 对象来代替它,代码的行为仍然是一样的。
但是,上面的例子违反了Liskov替代原则。你不能用Penguin 类的对象替换Bird 类的对象。如果你这样做,程序将抛出一个异常。
class Bird {
public void eat() {
System.out.println("Bird eating...");
}
}
class FlightBird extends Bird {
public void fly() {
System.out.println("Bird flying...");
}
}
class FlightlessBird extends Bird {
}
4.ISP(接口隔离原则)
接口隔离原则指出,不应该强迫客户依赖它不使用的方法。
那么你是如何做到这一点的呢?嗯,通过使你的接口小而集中。
你应该把大的接口分割成更具体的接口,这些接口集中在一组特定的功能上,这样客户端就可以选择只依赖他们需要的功能。
5.DIP(依赖反转原则)
依赖性反转原则试图避免软件模块之间的紧密耦合。它指出,高级模块不应该依赖低级模块,而只应该依赖它们的抽象部分。简单地说,它建议你尽可能地使用接口而不是具体的实现。
这使得模块与它所依赖的实现细节脱钩。模块只知道它所依赖的行为,而不知道该行为是如何实现的。这使得你可以随时改变实现而不影响模块本身。


