每个开发者都应该知道的7个原则

1*9InfF_CQmGrAAPEv9sxT9g.png

软件开发是一门复杂的领域。是什么让高质量的软件与容易出错、充满错误的软件有所不同?答案通常在开发人员在编写代码时采用的核心原则中。

编程原则:优秀代码的基石

编程原则是卓越软件的基石。这些建议和最佳实践指导开发人员编写既功能强大又优雅、易维护和可扩展的代码。

在本文中,我们深入探讨了每个开发者工具包中都应该有的7个基本编程原则:

1. DRY 原则

DRY:不要重复自己 — 减少冗余的关键原则。如果你发现自己复制粘贴同一段代码超过两次,现在是考虑抽象的时候了。

考虑这种情况:你有三个函数,每个函数都以相同的方式格式化日期。与其在所有三个函数中都有重复的格式化代码,不如创建一个单一的辅助函数:

// 格式化日期的辅助函数function formatDate(date) {    return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \\\'0\\\')}-${String(date.getDate()).padStart(2, \\\'0\\\')}`;}
// 函数 1: 显示今天的日期function displayTodaysDate() { const today = new Date(); return formatDate(today);}
// 函数 2: 显示一周后的日期function displayDateOneWeekFromNow() { const oneWeekFromNow = new Date(); oneWeekFromNow.setDate(oneWeekFromNow.getDate() + 7); return formatDate(oneWeekFromNow);}
// 函数 3: 显示一个月前的日期function displayDateOneMonthAgo() { const oneMonthAgo = new Date(); oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); return formatDate(oneMonthAgo);

2. KISS 原则

KISS:保持简单,愚蠢 — 在你的代码中追求简单。例如,如果你写了一个复杂的 if-else 链,也许使用 switch 语句或字典会简化和美化结构:

之前:

function getErrorMessage(errorCode) {    if (errorCode = \\\'E001\\\') {      return \\\'Invalid input.\\\';    } else if (errorCode = \\\'E002\\\') {      return \\\'Connection timed out.\\\';    } else if (errorCode = \\\'E003\\\') {      return \\\'Database error.\\\';    } else if (errorCode = \\\'E004\\\') {      return \\\'File not found.\\\';    } else {      return \\\'Unknown error.\\\';    }}

重构后:

const ERROR_MESSAGES = {    \\\'E001\\\': \\\'Invalid input.\\\',    \\\'E002\\\': \\\'Connection timed out.\\\',    \\\'E003\\\': \\\'Database error.\\\',    \\\'E004\\\': \\\'File not found.\\\'};
function getErrorMessage(errorCode) { return ERROR_MESSAGES[errorCode] || \\\'Unknown error.\\\';}

3. SOLID 原则

SOLID 不是一个单一的原则,而是五个设计原则的集合。尽管它们根植于面向对象编程(OOP),但它们的智慧可以更广泛地应用。

1. 单一职责原则(SRP): 一个类应该只有一个改变的理由。这意味着每个类应该只有一个任务或功能,确保更容易维护和在更改过程中减少副作用。

考虑这个例子:

// 错误的方法
class UserManager { saveUser(user) { // 保存用户到数据库的逻辑 }
generateReport(user) { // 生成用户报告的逻辑 }}

更优雅的解决方案是将其拆分为两个各自处理单一职责的类:

// 正确的方法
class UserDatabase { save(user) { // 将用户数据保存到数据库 }}
class UserReport { generate(user) { // 为用户生成报告 }}

在上面的代码片段中,我们分担了责任:UserReport 处理用户的报告生成,而 UserDatabase 管理将用户数据保存到数据库。

2. 开闭原则(OCP): 软件组件应该对扩展开放,对修改关闭。这允许开发人员在不修改现有代码的情况下添加新功能,促进可重用性并减少错误。

假设你有一个 AreaCalculator 类,用于计算矩形的面积。现在,如果我们添加一个 CircleAreaCalculator 将需要修改。

// 错误的方法
class AreaCalculator { calculateArea(shape) { if (shape.type = \\\"circle\\\") { return 3.14 * shape.radius * shape.radius; } else if (shape.type = \\\"square\\\") { return shape.side * shape.side; } }}

相反,使用 OCP:我们从一个基础的 Shape 类扩展我们的形状,允许轻松添加新形状而不修改 AreaCalculator

// 正确的方法
class Shape { calculateArea() {}}
class Circle extends Shape { constructor(radius) { super(); this.radius = radius; }
calculateArea() { return 3.14 * this.radius * this.radius; }}
class Square extends Shape { constructor(side) { super(); this.side = side
; }
calculateArea() { return this.side * this.side; }}

3. 里氏替换原则(LSP): 子类应该能够替换其基类而不产生异常。这确保继承类保持其父类的属性和行为。

遵循 LSP,我们应该重构设计以确保正确的继承:

class Bird {    fly() {      // 通用飞行行为    }}
class Penguin extends Bird { // 企鹅不能飞,所以这个方法不应该在这里}

正确的方法是我们将形状从基本的 Shape 类扩展出来,允许轻松添加新的形状而不修改 AreaCalculator

4. 接口隔离原则(ISP): 类不应该被迫实现它们不使用的接口。相反,接口应该对其目的具体而清晰。

这意味着接口不应该有太多方法,尽量我们将小接口抽取出来,以便类可以只实现它们需要的接口,就像下面的例子:

// 错误的方法
interface Worker { work(); eat(); sleep(); swim();}
// 正确的方法
interface Worker { work();}
interface Eater { eat();}
interface Swimmer { swim();}

5. 依赖反转原则(DIP): 高层模块不应与低层模块纠缠在一起;它们都应依赖于抽象。例如在开关和设备的设计中可以找到:

// 错误的方法
class LightBulb { turnOn() {} turnOff() {}}
class Switch { constructor(bulb) { this.bulb = bulb; }
operate() { // 直接控制灯泡 }}

我们可以重构这样,以便 Switch 可以对任何实现 SwitchableDevice 的设备进行操作,而不仅仅是 LightBulb

// 正确的方法
class SwitchableDevice { turnOn() {} turnOff() {}}
class Bulb extends SwitchableDevice { // 实现 turnOn 和 turnOff 方法 }
class SwitchDIP { constructor(device) { this.device = device; }
operate() { // 控制设备 }}

4. YAGNI 原则

YAGNI,“你不会需要它”,警告不要在必要之前添加功能。

例如,如果你正在构建一个博客网站,并考虑添加一个基于用户写作的功能来预测用户的心情,但这对于网站正常运行并不是必需的,那么最好将其留在一边,至少现在是这样。

有不必要功能的应用:

class Blog {    constructor(posts) {      this.posts = posts;    }
addPost(post) { this.posts.push(post); }
displayPosts() { // 显示所有帖子 }
predictUserMoodBasedOnWritings(post) { // 预测情绪的复杂算法 // ... return \\\"Happy\\\"; // 只是一个示例情绪 }
notifyUserAboutMood(mood) { // 通知逻辑 console.log(`Based on your writing, you seem to be ${mood}`); }}

删除不必要功能后:

class Blog {    constructor(posts) {      this.posts = posts;    }
addPost(post) { this.posts.push(post); }
displayPosts() { // 显示所有帖子 }}

5. SoC 原则

SoC,或“关注点分离”,建议不同的功能区域应由不同且最小重叠的模块管理。

例如,在一个天气应用程序中,一个模块可以处理数据获取,另一个可以管理数据存储,另一个则可以控制用户界面。每个都有自己的关注点,与其他模块分开。

// 1. 数据获取模块
function fetchWeatherData(city) { const apiKey = \\\'YOUR_API_KEY\\\'; const response = fetch(`https://api.weather.com/v1/${city}?apiKey=${apiKey}`); return response.json();}
// 2. 数据存储模块
function storeWeatherData(data) { localStorage.setItem(\\\'weatherData\\\', JSON.stringify(data));}
// 3. 用户界面模块
function displayWeatherData(data) { const weatherBox = document.querySelector(\\\'#weather-box\\\'); weatherBox.innerHTML = `<h1>${data.city}</h1><p>${data.temperature}°C</p>`;}
// 在主应用程序函数中组合它们
function weatherApp(city) { const data = fetchWeatherData(city); storeWeatherData(data); displayWeatherData(data);}

6. LoD 原则

LoD(迪米特法则)是开发软件的一个指导原则,特别是面向对象的程序。在其一般形式中,LoD是松散耦合的一个具体案例。

想象一下餐厅的场景:顾客将订单(方法)交给服务员,然后服务员将订单交给厨师。顾客不直接与厨师互动。

class Customer {  constructor(waiter) {    this.waiter = waiter;  }
giveOrder(order) { console.log(\\\"Customer: I\\\'d like to order \\\" + order); this.waiter.takeOrder(order); }}
class Waiter { constructor(chef) { this.chef = chef; }
takeOrder(order) { console.log(\\\'Waiter: Order received - \\\' + order); this.chef.prepareOrder(order); }}
class Chef { prepareOrder(order) { console.log(\\\'Chef: Preparing \\\' + order); // Logic to prepare the food... }}

7. COI 原则

组合优于继承原则(COI)建议使用组合(将简单对象组合以创建更复杂的对象)而不是类继承。

想象一下你有一个类 Bird 和一个类 Airplane。它们都能飞,但有继承关系并不合理。相反,你可以有一个 CanFly 类,并将你的 Bird 和 Airplane 类与它组合。

// 错误的方法:继承
class CanFly { fly() { console.log(this.constructor.name + \\\' is flying!\\\'); }}
class BirdInherit extends CanFly {}
class AirplaneInherit extends CanFly {}

通过以下组合方法,你可以轻松地向 BirdCompose 或 AirplaneCompose 添加或删除功能,而无需进行结构更改或添加不必要的冗余,强调灵活性。

// 正确的方法:组合
class CanFlyComposition { fly() { console.log(\\\'Flying in the sky!\\\'); }}
class BirdCompose { constructor() { this.flyingCapability = new CanFlyComposition(); }
fly() { this.flyingCapability.fly(); }}
class AirplaneCompose { constructor() { this.flyingCapability = new CanFlyComposition(); }
fly() { this.flyingCapability.fly(); }}

总结

掌握这7个编程原则将显著提升你在软件开发和问题解决方面的方法。

原创文章,作者:小技术君,如若转载,请注明出处:https://www.sudun.com/ask/33903.html

(0)
小技术君的头像小技术君
上一篇 2024年4月12日
下一篇 2024年4月12日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注