必须知道:编程的基本工程原理
1.不要复制自己(DRY原则)
2.迪米特法则(LoD)
3. KISS(保持简单,愚蠢)
4. YAGNI(你不需要它)
5. SoC(关注点分离)
6. 童子军规则(重构)
7. TDA(告诉而不是询问)
8. P^3(P立方原理)
大家好!这篇文章是我在OhMyScript上发表的原文章的重写版本,它涵盖了所有基本的工程编程原则,可以帮助你成为更好的开发人员,并遵循和维护干净的代码。
我们需要时刻提醒自己,我们编写的代码也会被其他人/开发者使用。请不要给他人的生活带来麻烦,因此,编写易于理解、简洁明了的代码非常重要,这样别人就不会抓狂,也不会觉得代码杂乱无章。
大多数程序员和开发人员都在不断努力提升自己,通过学习新的技术栈、新技术和工具并精通它们。但在编程或解决问题时,我们常常会忽略一些基本规范。
什么使您成为一名优秀的程序员?
如果你问10个开发者同样的问题,你肯定会得到10个不同的答案。尽管答案用不同的语言表达,但它们很可能表达的是同一个意思。一年来,作为一名专业的开发者,我学到了很多东西,我希望这些东西在我本科期间就能派上用场,用来维护庞大的代码库。
附言:我在本科期间做的项目糟透了。完全不符合我在这里解释的原则。
从我的个人经历和遇到的问题来看,我认为成为一名优秀的程序员需要具备理解特定问题并提出最可行解决方案的技能,这些解决方案不仅能解决当前问题,还能为长远发展服务。我相信,除了紧跟新技术的步伐外,所有开发人员都应遵循以下一些基本原则:
1.不要复制自己(DRY原则)
顾名思义,“不要复制自己”原则,也称为 DRY 原则,只是建议我们不要在整个项目或代码库中复制代码。
编写代码时,请确保避免代码重复。这条原则建议我们“写一次,用两次”。
从长远来看,随着新需求的出现,重复的代码将难以管理和维护。
下面显示了相同的简单示例,其中非 DRY 方法至少是您可以想象的,如果巧克力少于 5 个。随着巧克力的大小/数量的增加,使用非 DRY 方法管理此类代码将变得非常困难。
let costofChocolate = [10,12,15,20];
/**
** Non - DRY Approach
** Suppose you need to add ₹ 2 as tax for each
**/
costofChocolates[0] = costofChocolate[0] + 2;
costofChocolates[1] = costofChocolate[0] + 2;
costofChocolates[2] = costofChocolate[0] + 2;
costofChocolates[3] = costofChocolate[0] + 2;
/**
** DRY Approach
** Suppose you need to add ₹ 2 as tax for each
**/
function addTax(chocolatesCost,taxAmount) {
for(let i =0; i<chocolatesCost.length;i++){
chocolatesCost[i]=chocolatesCost[i]+taxAmount;
}
return chocolatesCost
}
addTax(costofChocolate, 2);
除了避免重复之外,这还能提高代码的可读性,并允许在项目中的任何其他组件/部分中复用特定功能。DRY 最大的优点是可维护性。如果确实存在需要修复的 bug,只需在一个地方进行修补,而不是分散在多个地方。
笔记:
- 有时,我们需要非常小心地遵循 DRY 原则。因为有时,一对代码片段可能看起来很相似,但实际上却有细微的差别。
- 避免过早进行 DRY 优化。
2.迪米特法则(LoD)
迪米特法则是一项设计原则,也称为最少知识原则。该法则最初指出:
对于所有类 C 以及所有附加到 C 的方法 M,M 向其发送消息的所有对象都必须是
M 的参数对象,包括自身对象或
C语言的实例变量对象
(由 M 创建的对象,或由 M 调用的函数或方法创建的对象,以及全局变量中的对象被视为 M 的参数。)
最初,当 Simula 进入市场时,它是第一种具有面向对象原则特征的语言;对象只是被用作将数据从一种方法传输到另一种方法的媒介。
“对象”背后的基本思想是相互传递数据,即对象之间进行通信。如果你读过原始定律,你会发现它仅仅意味着以下一般性的东西:
- 对象应该只处理它们的直接邻居(邻居->方法或数据)
- 对象不应该依赖于另一个邻居
- 对象应该只公开其他实体使用的信息
让我解释一下这个简单的例子;
/**
** Simple Example of Law of Demeter in JavaScript
**
** Assume an object userObj of the class User
**
**/
const userObj = new User();
userObj.getUsers().filterAge(); // Breaches the Law of Demeter
let userList = userObj.getUsers() // Breaches the Law of Demeter
let filterUsers = userObj.filterAge(); // Does not breach the Law of Demeter
/*
** Even while structuring / formatting the data
**
** User's designation is to be accessed from the variable
*/
user.designation._id // Breaches
user.designation.designationName // Breaches
user.designationId // Does not breach
user.designationName // Does not breach
该定律确保系统具有解耦的系统设计。
3. KISS(保持简单,愚蠢)
我坚信,当 KISS 是“保持简单和智能”的缩写时,它更有意义。

保持简单,愚蠢是一个很棒的生活窍门!
正如那句名言所说,
“一切都应该尽可能简单,而不是过于简单”
- 阿尔伯特·爱因斯坦
作为程序员,你编写的代码或设计应该精简,力求尽可能简洁。
有时我们会遇到复杂的问题陈述或需求。大多数时候,解决方案很简单,我们却不知道该如何处理。
在开始解决问题之前,务必先了解问题陈述。很多时候,解决方案是现成的,但我们往往没有规划好如何编写解决方案;即使找到了解决方案,我们也很少会去检查这是否是最佳、最优的解决方法。
最简单的例子,当我们开始成为一名开发人员时,我们总是无法遵循,
/**
** Simple Example of Short Circuit Evaluation in JavaScript
**
** This is first thing we learn in C, C++ or Java when we learn
** expressions & operators, yet fail to apply this.
**
**
** Assuming you want to console a variable; only if the variable username
** is defined and not null
**
**/
// Breaching the KISS
if(username == undefined || username == null || username == ''){
console.log('Error');
}
else {
console.log(username);
}
//Does not reach the KISS Principle
console.log( username || 'Error' );
Node 的异步操作就是 KISS 原则的最佳例证。想知道它是如何做到的吗?最初我们使用回调函数来处理异步函数。为了简化操作,Node 开发者们转而使用 Promise。为了进一步简化,Node 开发者们最终提出了 async / await。明白了吗?当然,使用过 JavaScript 框架或库的人一定深谙此道(处理回调背后的痛苦)😭,也一定明白 KISS 原则的重要性(有了 Async/Await 之后,生活变得多么轻松)😎
4. YAGNI(你不需要它)
作为开发人员,我们总是试图对项目的未来进行过度思考。我们试图基于“我们以后可能会用到”或“我们最终会需要它们”这样的假设来编写一些额外的功能。
答案是“YAGNI——你不需要它”;设计和开发所需的东西,避免不必要的或仅仅预见到的需求和功能。
每个开发人员肯定都经历过这个阶段,我自己也犯过这个错误。我开发了一些额外的功能,虽然客户没有要求,但我认为这些功能将来可能会有用,但最终客户想要的最终系统却与我预想的完全不同。
为什么要使用 YAGNI?
很可能你以后根本不需要它,而且只会浪费时间。如果你采用的是敏捷或增量式软件开发模型,你无法一次性获得完整的需求。避免给你的项目增加不必要的负担。
构建所需的一切!不要成为巫师
简而言之,活在当下,而不是未来;确保你为未来做好准备。
我举一个简单的例子,可能听起来有点模糊,但你肯定能理解。
/**
** For the first iteration requirement was to design a simple React Web - ** App to manage and view meetings
**
** A backend developer, builds the requirements and then spends adequate
** amount of time on creating a socket for adding real-time notification
** based on his assumptions that it would be needed for Mobile App in
** near future.
**
** In the second iteration, they finalize that project is confined to only
** as Web-App and there is no scope for Mobile App for this at all.
**
**
** What's the whole point of investing so much time and implementing it
** when it was not asked in the first place?
**
**/
5. SoC(关注点分离)
作为开发人员或人类,我们始终无法实现的最基本的主要原则之一就是关注点分离。
看看这看起来有多乱?
想象一下,如果你不根据关注点来区分它们,你的代码库会是什么样子。
作为开发人员,我们经常会犯一个简单的错误,那就是把太多东西捆绑到一个类/函数中。我们设计功能的方式是,希望用一个函数、类或对象“完成所有事情”。这种设计问题解决方案的方法是错误的,而且从长远来看,维护起来会非常繁琐。
要做一件大事,先把它分解成小事
- 匿名的
始终保持高水平的抽象;最简单的例子是 MVP 设计(模型视图呈现器设计);其中设计分为三个部分:模型处理数据,另一个呈现器处理用户界面或用户视图。
如上例所示,医生和护士的职责是独特的、独立的和明确的,因此每个人更易于管理和维护。
另一个简单的例子如下,
上面的例子展示了我们如何分离样式和 HTML 内容;基本上是外部化 CSS 文件。
6. 童子军规则(重构)
如果您曾经是学校童子军的一员,您一定知道这条简单的规则:“离开时,营地要比您来时更干净”。
这条规则也适用于软件开发。在实现新功能或处理遗留代码时,我们无法确定的一件事是它如何影响现有代码的质量。
我们不会在现有代码中寻找技术债务,而是在其基础上构建新功能。这最终会颠覆整个系统,并在某个时刻破坏代码,而这绝对是我们不希望发生的事情。
重构是关键。重构简单来说就是改变结构,但不改变其实现或最终结果。
最简单的例子:
耳机改版为耳塞:携带方便,成本更低
同样,我们应该重构我们的代码库,以便更好地理解、可读性和易于维护,也可能提高效率和优化执行。
/**
** Before Refactoring
**/
function getAddress(latitude, longitude){}
function getCountry(latitude, longitude){}
function getCity(latitude, longitude){}
/**
** After Refactoring ::
** Better readability and maintain function-arity (<3-4 No. of Arguments)
**/
function getAddress(coordinates){}
function getCountry(coordinates){}
function getCity(coordinates){}
注意:
避免不必要的优化/重构
7. TDA(告诉而不是询问)
“告诉而不是询问”是基本原则,它提醒人们面向对象就是用处理数据的方法封装数据。是不是有点困惑?
当您想要从类访问数据时,切勿使用对象来访问它,而是通过请求该数据的方法,以一种更简单的方式,即大家都听说过的 getter/setter。
TDA 建议执行某些操作总是比直接访问数据更好。
TDA 的简单示例如下,
/**
** Non TDA Approach
**/
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const userObj = new User('OhMyScript', '22');
console.log(userObj.name); // Breaches TDA
console.log(userObj.age); // Breaches TDA
/**
** TDA Approach
**/
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName(){
return this.name;
}
getAge(){
return this.age;
}
}
const userObj = new User('OhMyScript', '22');
console.log(userObj.getName()); // Does not breach TDA
console.log(userObj.getAge()); // Does not breach TDA
8. P^3(P立方原理)
这不是编程原则,而是我坚信的通用开发者原则,也是唯一能帮助你精通上述所有原则的方法。熟能生巧,熟能生巧。
这些原则并非可以学习和应用的。这与我们听到的关于陈年葡萄酒的说法非常相似。
这些是一些最重要的基本原则,它们在你的开发者生涯中发挥着重要作用。我确信可能还有很多我可能遗漏的原则。
了解 SOLID 原则的朋友,请继续关注下一篇文章。SOLID 原则是面向对象编程中非常重要的设计原则之一。我决定为此专门写一篇文章。
如果您喜欢这篇文章,请点击“赞”按钮,分享文章并订阅博客。如果您想让我撰写关于我所负责的特定领域/技术的文章,请随时发送邮件至shravan@ohmyscript.com。
请继续关注我关于 SOLID 编程原则的下一篇文章。
请订阅我的博客OhMyScript,获取相关文章。敬请期待更多精彩内容。
就到这里吧。感谢您的阅读。
下次再见。
祝您学习愉快。